claude_agent 0.7.12 → 0.7.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/rules/testing.md +51 -10
  3. data/.claude/settings.json +1 -0
  4. data/ARCHITECTURE.md +237 -0
  5. data/CHANGELOG.md +53 -0
  6. data/CLAUDE.md +2 -0
  7. data/README.md +46 -1
  8. data/Rakefile +17 -0
  9. data/SPEC.md +214 -125
  10. data/lib/claude_agent/client/commands.rb +225 -0
  11. data/lib/claude_agent/client.rb +4 -206
  12. data/lib/claude_agent/content_blocks/generic_block.rb +39 -0
  13. data/lib/claude_agent/content_blocks/image_content_block.rb +54 -0
  14. data/lib/claude_agent/content_blocks/server_tool_result_block.rb +22 -0
  15. data/lib/claude_agent/content_blocks/server_tool_use_block.rb +48 -0
  16. data/lib/claude_agent/content_blocks/text_block.rb +19 -0
  17. data/lib/claude_agent/content_blocks/thinking_block.rb +19 -0
  18. data/lib/claude_agent/content_blocks/tool_result_block.rb +25 -0
  19. data/lib/claude_agent/content_blocks/tool_use_block.rb +134 -0
  20. data/lib/claude_agent/content_blocks.rb +8 -335
  21. data/lib/claude_agent/control_protocol/commands.rb +304 -0
  22. data/lib/claude_agent/control_protocol/lifecycle.rb +116 -0
  23. data/lib/claude_agent/control_protocol/messaging.rb +163 -0
  24. data/lib/claude_agent/control_protocol/primitives.rb +168 -0
  25. data/lib/claude_agent/control_protocol/request_handling.rb +231 -0
  26. data/lib/claude_agent/control_protocol.rb +50 -882
  27. data/lib/claude_agent/conversation.rb +8 -1
  28. data/lib/claude_agent/event_handler.rb +1 -0
  29. data/lib/claude_agent/get_session_info.rb +86 -0
  30. data/lib/claude_agent/hooks.rb +23 -2
  31. data/lib/claude_agent/list_sessions.rb +22 -13
  32. data/lib/claude_agent/message_parser.rb +26 -4
  33. data/lib/claude_agent/messages/conversation.rb +138 -0
  34. data/lib/claude_agent/messages/generic.rb +39 -0
  35. data/lib/claude_agent/messages/hook_lifecycle.rb +158 -0
  36. data/lib/claude_agent/messages/result.rb +80 -0
  37. data/lib/claude_agent/messages/streaming.rb +84 -0
  38. data/lib/claude_agent/messages/system.rb +67 -0
  39. data/lib/claude_agent/messages/task_lifecycle.rb +240 -0
  40. data/lib/claude_agent/messages/tool_lifecycle.rb +95 -0
  41. data/lib/claude_agent/messages.rb +11 -829
  42. data/lib/claude_agent/options/serializer.rb +194 -0
  43. data/lib/claude_agent/options.rb +11 -176
  44. data/lib/claude_agent/query.rb +0 -2
  45. data/lib/claude_agent/sandbox_settings.rb +3 -0
  46. data/lib/claude_agent/session.rb +0 -204
  47. data/lib/claude_agent/session_mutations.rb +148 -0
  48. data/lib/claude_agent/transport/subprocess.rb +2 -2
  49. data/lib/claude_agent/types/mcp.rb +30 -0
  50. data/lib/claude_agent/types/models.rb +146 -0
  51. data/lib/claude_agent/types/operations.rb +38 -0
  52. data/lib/claude_agent/types/sessions.rb +50 -0
  53. data/lib/claude_agent/types/tools.rb +32 -0
  54. data/lib/claude_agent/types.rb +6 -264
  55. data/lib/claude_agent/v2_session.rb +207 -0
  56. data/lib/claude_agent/version.rb +1 -1
  57. data/lib/claude_agent.rb +37 -3
  58. data/sig/claude_agent.rbs +144 -13
  59. metadata +33 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 22706a6a24af11c5f24404ee12add5ff617a6c293e5869c5437d295f6314c9dd
4
- data.tar.gz: e55b7eaba0ede01618c88971992651c5f4d09253333b31abf6ff52fb52098464
3
+ metadata.gz: 6b6188550282e29001c6bf2996842b5c11cd013de37aeeded995c982cef67399
4
+ data.tar.gz: 33e7d2e6b000d5f085093f236fd531c136e3711be170c3af4c11715fbdedda77
5
5
  SHA512:
6
- metadata.gz: 325f9c1e944e5eaaa51f7a5ae1029e1bf93016ce8abcfd9983185771830b9be2d560093a1c42d70dd1a9755b1c501dbad2f4e46e873dc3b7c424677c3280d5e4
7
- data.tar.gz: 9bddde78dec0a0dd251cc9fc77c6379b9206469e18ce4310d6e444b231e4f65ce137235d89422778e2a2bd09b258254801479ea98ea3092bdef7b0c855fccafc
6
+ metadata.gz: b5ec2de8caa5ef32681e671645dd6d6540d58f86221c3d99186cc7ab7e9a076b892fe41c16e2472b94399c296439b5ceba72f573eb0b218b8b2ff507661d162f
7
+ data.tar.gz: c00a1312f295f1f15949cac076155750b2259f5412cfb0cbe6acffa8bcb789927887bb58ebacdc94fd92fa492851f985b975a14bb31abe68b78e23ce4fda8c5d
@@ -7,12 +7,14 @@ SDK-specific testing guidance. For general patterns (base classes, mocking, stru
7
7
  ```bash
8
8
  bundle exec rake test # Unit tests only
9
9
  bundle exec rake test_integration # Integration tests (requires CLI v2.0.0+)
10
+ bundle exec rake test_smoke # Smoke tests against local LLM (e.g. Ollama)
10
11
  bundle exec rake test_all # All tests
11
12
  bundle exec ruby -Itest test/claude_agent/test_foo.rb # Single file
12
13
 
13
14
  # Binstubs
14
15
  bin/test # Unit tests only
15
16
  bin/test-integration # Integration tests
17
+ bin/test-smoke # Smoke tests (Ollama)
16
18
  bin/test-all # All tests
17
19
  ```
18
20
 
@@ -22,6 +24,7 @@ bin/test-all # All tests
22
24
  test/
23
25
  ├── test_helper.rb # Central setup, requires, base class
24
26
  ├── integration_helper.rb # Base class for integration tests
27
+ ├── smoke_helper.rb # Base class for smoke tests (Ollama)
25
28
  ├── support/ # Shared mocks, test transports, helpers
26
29
  │ └── mock_transport.rb
27
30
  ├── claude_agent/ # Unit tests (mirrors lib/claude_agent/)
@@ -29,10 +32,15 @@ test/
29
32
  │ ├── test_options.rb
30
33
  │ └── mcp/
31
34
  │ └── test_tool.rb
32
- ├── integration/ # Integration tests (require Claude CLI)
33
- │ ├── test_query.rb
34
- │ ├── test_client.rb
35
- └── ...
35
+ ├── integration/ # Scenario tests (require Claude CLI)
36
+ │ ├── test_query_scenarios.rb
37
+ │ ├── test_client_scenarios.rb
38
+ ├── test_conversation_scenarios.rb
39
+ │ ├── test_session_scenarios.rb
40
+ │ ├── test_permissions_and_tools_scenarios.rb
41
+ │ └── test_transport.rb
42
+ ├── smoke/ # Smoke tests (local LLM via Ollama)
43
+ │ └── test_basic.rb
36
44
  └── fixtures/ # JSON fixtures for parser tests
37
45
  ├── assistant_message.json
38
46
  └── tool_use_response.json
@@ -225,19 +233,27 @@ end
225
233
 
226
234
  ## Integration Tests
227
235
 
236
+ Integration tests are **scenario tests** that consolidate multiple assertions per CLI process spawn. Each test exercises a complete workflow rather than testing a single field.
237
+
228
238
  Integration tests live in `test/integration/` and inherit from `IntegrationTestCase`:
229
239
 
230
240
  ```ruby
231
- # test/integration/test_query.rb
241
+ # test/integration/test_query_scenarios.rb
232
242
  require_relative "../integration_helper"
233
243
 
234
- class TestIntegrationQuery < IntegrationTestCase
235
- test "real query returns result" do
236
- messages = ClaudeAgent.query(prompt: "Say hello", options: test_options).to_a
237
- result = messages.find { |m| m.is_a?(ClaudeAgent::ResultMessage) }
244
+ class TestIntegrationQueryScenarios < IntegrationTestCase
245
+ test "query lifecycle: messages, fields, and content" do
246
+ messages = ClaudeAgent.query(prompt: "Reply with exactly: HELLO", options: test_options).to_a
238
247
 
248
+ # Assert system, assistant, and result messages in one spawn
249
+ system_msg = messages.find { |m| m.is_a?(ClaudeAgent::SystemMessage) }
250
+ assert_not_nil system_msg
251
+ assert_equal "init", system_msg.subtype
252
+
253
+ result = messages.find { |m| m.is_a?(ClaudeAgent::ResultMessage) }
239
254
  assert_not_nil result
240
- assert result.success?
255
+ assert_equal false, result.is_error
256
+ assert_not_nil result.session_id
241
257
  end
242
258
  end
243
259
  ```
@@ -247,6 +263,31 @@ The `IntegrationTestCase` base class:
247
263
  - Skips if Claude CLI is not installed
248
264
  - Provides `test_options` helper with sensible defaults
249
265
 
266
+ ## Smoke Tests
267
+
268
+ Smoke tests run the same core paths against a local LLM (e.g. Ollama) for fast feedback without Anthropic API costs.
269
+
270
+ Smoke tests live in `test/smoke/` and inherit from `SmokeTestCase`:
271
+
272
+ ```ruby
273
+ # test/smoke/test_basic.rb
274
+ require_relative "../smoke_helper"
275
+
276
+ class TestSmokeBasic < SmokeTestCase
277
+ test "basic query returns result" do
278
+ messages = ClaudeAgent.query(prompt: "Reply with exactly: PING", options: test_options).to_a
279
+ result = messages.find { |m| m.is_a?(ClaudeAgent::ResultMessage) }
280
+ assert_not_nil result
281
+ end
282
+ end
283
+ ```
284
+
285
+ The `SmokeTestCase` base class:
286
+ - Extends `IntegrationTestCase` (requires CLI + INTEGRATION=true)
287
+ - Skips unless `SMOKE=true` is set (automatic with `rake test_smoke`)
288
+ - Checks Ollama availability before running
289
+ - `rake test_smoke` auto-sets `ANTHROPIC_BASE_URL`, `ANTHROPIC_API_KEY`, and `ANTHROPIC_AUTH_TOKEN`
290
+
250
291
  ## What to Test
251
292
 
252
293
  | Component | Focus Areas |
@@ -30,6 +30,7 @@
30
30
  "Bash(bin/setup:*)",
31
31
  "Bash(bin/test:*)",
32
32
  "Bash(bin/test-integration:*)",
33
+ "Bash(bin/test-smoke:*)",
33
34
  "Bash(bin/test-all:*)",
34
35
  "Bash(bin/update-reference-sdks:*)",
35
36
  "WebFetch(domain:docs.anthropic.com)",
data/ARCHITECTURE.md ADDED
@@ -0,0 +1,237 @@
1
+ # TypeScript SDK Architecture: Data Flow
2
+
3
+ How data flows through the `@anthropic-ai/claude-agent-sdk`. Not about specific classes — about **concepts**, what they wrap, and where data lives.
4
+
5
+ ## The Big Picture
6
+
7
+ The SDK is a **process bridge**. It spawns the Claude Code CLI as a subprocess and communicates over JSON Lines via stdin/stdout. Everything flows through this pipe.
8
+
9
+ ```mermaid
10
+ graph TB
11
+ subgraph "SDK Process (your app)"
12
+ USER["Your Code"]
13
+ Q["query() / SDKSession"]
14
+ OPT["Options"]
15
+ CP["Control Protocol"]
16
+ HOOKS["Hook Callbacks"]
17
+ MCP_SDK["SDK MCP Servers"]
18
+ PERM["canUseTool Callback"]
19
+ end
20
+
21
+ subgraph "CLI Subprocess"
22
+ CLI["Claude Code CLI"]
23
+ CLAUDE_API["Claude API"]
24
+ MCP_EXT["External MCP Servers"]
25
+ TOOLS["Built-in Tools\n(Bash, Read, Edit...)"]
26
+ end
27
+
28
+ USER -->|"prompt + options"| Q
29
+ Q -->|"builds CLI args + env"| OPT
30
+ OPT -->|"spawns process"| CLI
31
+ Q <-->|"JSON Lines\nstdin/stdout"| CP
32
+ CP <-->|"control_request/\ncontrol_response"| CLI
33
+ CLI <-->|"API calls"| CLAUDE_API
34
+ CLI <-->|"stdio/SSE/HTTP"| MCP_EXT
35
+ CLI -->|"executes"| TOOLS
36
+ CP -->|"routes"| HOOKS
37
+ CP -->|"routes"| MCP_SDK
38
+ CP -->|"routes"| PERM
39
+ ```
40
+
41
+ ## Two Message Channels on One Pipe
42
+
43
+ All messages flow through one stdin/stdout pipe, but the Control Protocol splits them into two logical channels:
44
+
45
+ ```mermaid
46
+ graph LR
47
+ subgraph "stdout from CLI"
48
+ RAW["Raw JSON Lines"]
49
+ end
50
+
51
+ RAW -->|"type: control_request\ntype: control_response"| CTRL["Control Channel\n(handled in-process)"]
52
+ RAW -->|"type: assistant\ntype: result\ntype: system\ntype: user\n..."| SDK_Q["SDK Channel\n(queued for your code)"]
53
+
54
+ CTRL -->|"can_use_tool"| PERM["Permission Callback"]
55
+ CTRL -->|"hook_callback"| HOOK["Hook Callbacks"]
56
+ CTRL -->|"mcp_message"| MCP["SDK MCP Server"]
57
+ CTRL -->|"elicitation"| ELICIT["Elicitation Callback"]
58
+
59
+ SDK_Q --> PARSE["Message Parser"]
60
+ PARSE --> TYPED["Typed SDK Messages\n(SDKMessage union)"]
61
+ ```
62
+
63
+ ## Data Flow: A Complete Turn
64
+
65
+ ```mermaid
66
+ sequenceDiagram
67
+ participant App as Your Code
68
+ participant Q as query()
69
+ participant CP as Control Protocol
70
+ participant T as Transport (stdin/stdout)
71
+ participant CLI as Claude Code CLI
72
+ participant API as Claude API
73
+
74
+ Note over Q,T: 1. SETUP
75
+ App->>Q: query({ prompt, options })
76
+ Q->>T: spawn("claude", [...args])
77
+ T->>CLI: process starts
78
+ Q->>CP: start(streaming: true)
79
+ CP->>T: write: {type: "control_request",\nrequest: {subtype: "initialize",\nhooks, sdkMcpServers, agents...}}
80
+ CLI-->>T: {type: "control_response",\nresponse: {commands, models, account...}}
81
+ CP-->>Q: initialized
82
+
83
+ Note over Q,T: 2. SEND PROMPT
84
+ CP->>T: write: {type: "user",\nmessage: {role: "user", content: "..."}}
85
+
86
+ Note over CLI,API: 3. CLI PROCESSES
87
+ CLI->>API: messages.create(...)
88
+ API-->>CLI: streaming response
89
+
90
+ Note over Q,T: 4. PERMISSION CHECK (if tool use)
91
+ CLI-->>T: {type: "control_request",\nrequest: {subtype: "can_use_tool",\ntool_name: "Bash"...}}
92
+ CP->>App: canUseTool("Bash", input, options)
93
+ App-->>CP: {behavior: "allow"}
94
+ CP->>T: write: {type: "control_response",\nresponse: {behavior: "allow"}}
95
+
96
+ Note over Q,T: 5. STREAM RESULTS
97
+ CLI-->>T: {type: "assistant",\nmessage: {content: [...]}}
98
+ T-->>CP: routes to SDK queue
99
+ CP-->>Q: SDKAssistantMessage
100
+ Q-->>App: yield message
101
+
102
+ CLI-->>T: {type: "result",\nsubtype: "success", result: "...", usage: {...}}
103
+ T-->>CP: routes to SDK queue
104
+ CP-->>Q: SDKResultMessage
105
+ Q-->>App: yield message (iteration ends)
106
+ ```
107
+
108
+ ## What Wraps CLI Data vs. What's an SDK Abstraction
109
+
110
+ ### SDK Abstractions
111
+
112
+ Invented by the SDK — not in the CLI's JSON protocol.
113
+
114
+ | Concept | What it does |
115
+ |------------------------|-------------------------------------------------------------------------------------------------------------------------|
116
+ | `query()` | Entry point. Returns `AsyncGenerator<SDKMessage>`. Spawns process, runs handshake, sends prompt, yields typed messages. |
117
+ | `SDKSession` | V2 multi-turn session interface. `.send()` / `.stream()` / `.close()`. |
118
+ | `Options` | Converts user config into CLI args + env vars. The SDK's configuration surface. |
119
+ | Control Protocol | Routes messages between two channels. Manages request/response matching with IDs. |
120
+ | `canUseTool` | Permission callback. CLI sends a control request, SDK routes it to your function. |
121
+ | `HookCallback` | In-process hook execution. CLI sends hook input, SDK calls your function, returns result. |
122
+ | `createSdkMcpServer()` | Hosts an MCP server in your process. CLI routes MCP messages to it via control protocol. |
123
+ | `onElicitation` | User input callback for MCP OAuth flows. |
124
+ | Query methods | `.interrupt()`, `.setModel()`, `.rewindFiles()`, `.setMcpServers()` — all send control requests. |
125
+ | `listSessions()` | Reads the CLI's session JSONL files from disk. No subprocess involved. |
126
+ | `getSessionMessages()` | Parses the CLI's transcript format from disk. |
127
+
128
+ ### CLI Protocol Wrappers
129
+
130
+ Direct 1:1 mapping of JSON the CLI sends/receives. The SDK adds TypeScript types but doesn't transform the data.
131
+
132
+ | Type | CLI JSON it wraps |
133
+ |----------------------------------------|--------------------------------------------------------------------------------------------------------------|
134
+ | `SDKAssistantMessage` | `{type: "assistant", message: BetaMessage}` |
135
+ | `SDKUserMessage` | `{type: "user", message: MessageParam}` |
136
+ | `SDKResultMessage` | `{type: "result", subtype: "success"\|"error_*"}` |
137
+ | `SDKSystemMessage` | `{type: "system", subtype: "init"}` |
138
+ | `SDKStatusMessage` | `{type: "system", subtype: "status"}` |
139
+ | `SDKPartialAssistantMessage` | `{type: "stream_event", event: ...}` |
140
+ | `SDKHookStarted/Progress/Response` | Hook lifecycle messages |
141
+ | `SDKTaskStarted/Progress/Notification` | Subagent task messages |
142
+ | `SDKToolProgressMessage` | Tool execution progress |
143
+ | `SDKRateLimitEvent` | Rate limit state changes |
144
+ | `SDKCompactBoundaryMessage` | Context compaction markers |
145
+ | `SDKFilesPersistedEvent` | File checkpoint events |
146
+ | `SDKElicitationCompleteMessage` | MCP elicitation completion |
147
+ | `SDKPromptSuggestionMessage` | Predicted next prompt |
148
+ | Control request/response types | `initialize`, `can_use_tool`, `hook_callback`, `mcp_message`, `interrupt`, `set_model`, `rewind_files`, etc. |
149
+
150
+ ### Configuration Wrappers
151
+
152
+ SDK types that map to CLI flags, env vars, or config JSON.
153
+
154
+ | SDK Type | Maps to |
155
+ |------------------------|----------------------------------------------|
156
+ | `PermissionMode` | `--permission-mode` flag |
157
+ | `McpStdioServerConfig` | MCP server config JSON |
158
+ | `AgentDefinition` | `--agents` JSON config |
159
+ | `SandboxSettings` | `--sandbox-settings` JSON |
160
+ | `OutputFormat` | `--output-format` config |
161
+ | `ThinkingConfig` | `--thinking` / `--max-thinking-tokens` |
162
+ | `SystemPrompt` | `--system-prompt` / `--append-system-prompt` |
163
+ | `SettingSource` | `--settings-sources` flag |
164
+
165
+ ### Pass-through Types
166
+
167
+ Types from upstream protocols that flow through untouched. The SDK re-exports them for convenience.
168
+
169
+ | Type | Source |
170
+ |-----------------------------|-----------------------------------------------|
171
+ | `BetaMessage` | `@anthropic-ai/sdk` (Anthropic API response) |
172
+ | `BetaUsage` | `@anthropic-ai/sdk` (token counts from API) |
173
+ | `MessageParam` | `@anthropic-ai/sdk` (API input format) |
174
+ | `BetaRawMessageStreamEvent` | `@anthropic-ai/sdk` (streaming chunks) |
175
+ | `JSONRPCMessage` | `@modelcontextprotocol/sdk` (MCP wire format) |
176
+ | `CallToolResult` | `@modelcontextprotocol/sdk` (MCP tool output) |
177
+ | `ElicitResult` | `@modelcontextprotocol/sdk` (MCP user input) |
178
+
179
+ ## The Three Layers
180
+
181
+ ```mermaid
182
+ graph TB
183
+ subgraph L3["Layer 3: SDK Abstractions"]
184
+ direction LR
185
+ query["query()"]
186
+ session["SDKSession"]
187
+ opts["Options builder"]
188
+ ctrl["Control Protocol"]
189
+ callbacks["Callbacks\n(canUseTool, hooks,\nonElicitation)"]
190
+ session_mgmt["Session Management\n(listSessions,\ngetSessionMessages)"]
191
+ end
192
+
193
+ subgraph L2["Layer 2: CLI Protocol Types"]
194
+ direction LR
195
+ msgs["SDKMessage union\n(21+ message types)"]
196
+ ctrl_req["Control Requests\n(initialize, can_use_tool,\nhook_callback, ...)"]
197
+ ctrl_resp["Control Responses"]
198
+ end
199
+
200
+ subgraph L1["Layer 1: Pass-through Types"]
201
+ direction LR
202
+ beta["BetaMessage\n(Anthropic API)"]
203
+ usage["BetaUsage\n(Anthropic API)"]
204
+ msgparam["MessageParam\n(Anthropic API)"]
205
+ jsonrpc["JSONRPCMessage\n(MCP protocol)"]
206
+ content["Content Blocks\n(text, tool_use, thinking)"]
207
+ end
208
+
209
+ L3 --> L2
210
+ L2 --> L1
211
+
212
+ style L3 fill:#e1f5fe
213
+ style L2 fill:#fff3e0
214
+ style L1 fill:#f3e5f5
215
+ ```
216
+
217
+ **Layer 1 (Pass-through):** Types from the Anthropic API and MCP protocol. The CLI's `SDKAssistantMessage.message` is literally a `BetaMessage` from the API. The SDK doesn't interpret content blocks — they're whatever the API returned.
218
+
219
+ **Layer 2 (CLI Protocol):** The JSON shapes the CLI sends/receives over JSON Lines. The SDK defines TypeScript types for them but doesn't transform the data. It's a typed window into what the CLI emits.
220
+
221
+ **Layer 3 (SDK Abstractions):** Things the SDK invents. `query()` is not a CLI concept — it's an ergonomic wrapper that spawns a process, runs the control protocol handshake, sends the prompt, and yields typed messages. The Control Protocol's routing logic is an SDK concept. The CLI just sends a control request and blocks until it gets a response.
222
+
223
+ ## Key Insight: The SDK is Thin Where It Matters
224
+
225
+ The SDK deliberately avoids re-interpreting CLI data. It doesn't parse content blocks into rich objects, doesn't build conversation trees, doesn't accumulate state. It's a **typed streaming bridge**:
226
+
227
+ ```
228
+ Your prompt --> Options --> CLI args --> subprocess --> JSON Lines --> typed messages --> your code
229
+ ^ |
230
+ +-- control requests <---------+
231
+ (permissions, hooks, MCP)
232
+ ```
233
+
234
+ The "intelligence" lives in three places:
235
+ 1. **The CLI** — runs Claude, manages tools, handles the API
236
+ 2. **The Control Protocol** — routes bidirectional requests between CLI and your callbacks
237
+ 3. **Your code** — decides permissions, handles hooks, processes messages
data/CHANGELOG.md CHANGED
@@ -7,6 +7,59 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.14] - 2026-03-14
11
+
12
+ ### Fixed
13
+ - Replace busy-wait polling (`Queue#pop(true)` + `sleep 0.01`) in `ControlProtocol::Messaging#each_message` with blocking `Queue#pop` and `:done` sentinel, eliminating CPU waste and up-to-10ms per-message latency
14
+ - Fix `Conversation#partition_kwargs` misrouting `on_elicitation` (and any future `on_*` Options attributes) as event callbacks instead of forwarding to Options; now uses explicit allowlist derived from `EventHandler::EVENTS`
15
+ - Remove global `ENV["CLAUDE_CODE_ENTRYPOINT"]` mutation from `Client#connect` and `ClaudeAgent.query`; the subprocess already receives this via `Options#to_env`
16
+ - Log stderr callback errors instead of silently swallowing them in `Transport::Subprocess`
17
+
18
+ ## [0.7.13] - 2026-03-14
19
+
20
+ ### Added
21
+ - Ollama smoke test profile (`rake test_smoke`, `bin/test-smoke`) for fast local testing against local LLMs
22
+ - `SmokeTestCase` base class with Ollama availability check and configurable `SMOKE_MODEL` env var
23
+
24
+ ### Changed
25
+ - Restructured integration test suite: removed 92 misplaced unit tests, consolidated 52 CLI tests into 16 scenario tests across 5 files
26
+ - Integration tests now use scenario-based structure (`test_*_scenarios.rb`) that exercises multiple assertions per CLI process spawn
27
+ - Split `content_blocks.rb` (352 lines) into `content_blocks/` directory with 8 focused files + barrel file
28
+ - Split `types.rb` (300 lines) into `types/` directory with 5 domain-grouped files + barrel file
29
+ - Split `messages.rb` (908 lines) into `messages/` directory with 8 semantic-domain files + barrel file
30
+ - Extracted `control_protocol.rb` (1,010 lines) into 5 mixin modules (`Primitives`, `Lifecycle`, `Messaging`, `Commands`, `RequestHandling`) + shell class
31
+ - Extracted `Client::Commands` mixin from `client.rb` (545 lines) for CLI command delegations
32
+ - Extracted `Options::Serializer` mixin from `options.rb` (344 lines) for CLI arg/env serialization
33
+ - Split `session.rb` into `v2_session.rb` (V2 Session API) and `session.rb` (historical finder)
34
+ - Split monolithic test files to mirror lib/ directory structure (`test/claude_agent/{messages,content_blocks,types,control_protocol}/`)
35
+ - Added RBS module declarations for `ControlProtocol`, `Client`, and `Options` mixins
36
+
37
+ ### Added
38
+ - `AgentInfo` type with `name`, `description`, and `model` fields (TypeScript SDK v0.2.63 parity)
39
+ - `supported_agents` control request on `ControlProtocol` and `Client` for querying available subagents (TypeScript SDK v0.2.63 parity)
40
+ - `agents` field on `InitializationResult` returning `AgentInfo[]`
41
+ - `fast_mode_state` field on `ResultMessage` (TypeScript SDK v0.2.63 parity)
42
+ - `ElicitationCompleteMessage` for MCP elicitation completion events (TypeScript SDK v0.2.63 parity)
43
+ - `LocalCommandOutputMessage` for local command output events (TypeScript SDK v0.2.63 parity)
44
+ - `on_elicitation` option for handling MCP elicitation requests via callback (TypeScript SDK v0.2.63 parity)
45
+ - `Elicitation` and `ElicitationResult` hook events with input types (TypeScript SDK v0.2.63 parity)
46
+ - Elicitation control protocol handling with callback support and default decline behavior
47
+ - `on_elicitation_complete` and `on_local_command_output` event handler methods
48
+ - `tag` and `created_at` fields on `SessionInfo` (TypeScript SDK v0.2.75 parity)
49
+ - `supports_auto_mode` field on `ModelInfo` (TypeScript SDK v0.2.75 parity)
50
+ - `offset` parameter on `list_sessions` for pagination (TypeScript SDK v0.2.75 parity)
51
+ - `rename_session(session_id, title)` for renaming session files (TypeScript SDK v0.2.74 parity)
52
+ - `tag_session(session_id, tag)` for tagging sessions with Unicode sanitization (TypeScript SDK v0.2.75 parity)
53
+ - `get_session_info(session_id)` for single-session lookup by UUID (TypeScript SDK v0.2.75 parity)
54
+ - `agent_progress_summaries` option for periodic AI-generated progress summaries (TypeScript SDK v0.2.72 parity)
55
+ - `prompt` field on `TaskStartedMessage` (TypeScript SDK v0.2.75 parity)
56
+ - `summary` field on `TaskProgressMessage` for AI-generated progress summaries (TypeScript SDK v0.2.72 parity)
57
+ - `fast_mode_state` field on `InitializationResult` (TypeScript SDK v0.2.75 parity)
58
+ - RBS signatures for all new types, fields, and methods
59
+
60
+ ### Removed
61
+ - `get_settings` from SPEC.md — not in TypeScript SDK public API (`sdk.d.ts`)
62
+
10
63
  ## [0.7.12] - 2026-02-27
11
64
 
12
65
  ### Added
data/CLAUDE.md CHANGED
@@ -17,6 +17,7 @@ bin/setup # Install dependencies
17
17
  bundle exec rake # Run unit tests + rbs + rubocop (default)
18
18
  bundle exec rake test # Unit tests only
19
19
  bundle exec rake test_integration # Integration tests (requires CLI v2.0.0+)
20
+ bundle exec rake test_smoke # Smoke tests against local LLM (e.g. Ollama)
20
21
  bundle exec rake test_all # All tests (requires CLI v2.0.0+)
21
22
  bundle exec rake rbs # Validate RBS signatures
22
23
  bundle exec rake rbs:parse # RBS syntax check only (faster)
@@ -28,6 +29,7 @@ bin/console # IRB with gem loaded
28
29
  bin/test # Unit tests only
29
30
  bin/test-integration # Integration tests
30
31
  bin/test-all # All tests
32
+ bin/test-smoke # Smoke tests (Ollama)
31
33
  bin/rbs-validate # Validate RBS signatures
32
34
  bin/release VERSION # Release gem (e.g., bin/release 1.2.0)
33
35
  ```
data/README.md CHANGED
@@ -501,6 +501,7 @@ result.error? # Convenience method
501
501
  result.errors # Array of error messages (if any)
502
502
  result.permission_denials # Array of SDKPermissionDenial (if any)
503
503
  result.stop_reason # Why the model stopped generating (e.g. "end_turn", "tool_use")
504
+ result.fast_mode_state # Fast mode status (if applicable)
504
505
  ```
505
506
 
506
507
  ### UserMessageReplay
@@ -690,6 +691,27 @@ Suggested follow-up prompts (requires `prompt_suggestions: true`):
690
691
  suggestion.suggestion # The suggested prompt text
691
692
  ```
692
693
 
694
+ ### ElicitationCompleteMessage
695
+
696
+ MCP elicitation completion:
697
+
698
+ ```ruby
699
+ elicitation.uuid # Message UUID
700
+ elicitation.session_id # Session identifier
701
+ elicitation.mcp_server_name # MCP server that requested elicitation
702
+ elicitation.elicitation_id # Elicitation identifier
703
+ ```
704
+
705
+ ### LocalCommandOutputMessage
706
+
707
+ Local command output:
708
+
709
+ ```ruby
710
+ output.uuid # Message UUID
711
+ output.session_id # Session identifier
712
+ output.content # Command output content
713
+ ```
714
+
693
715
  ### GenericMessage
694
716
 
695
717
  Wraps unknown/future message types instead of raising errors:
@@ -1084,6 +1106,27 @@ update = ClaudeAgent::PermissionUpdate.new(
1084
1106
  )
1085
1107
  ```
1086
1108
 
1109
+ ## MCP Elicitation
1110
+
1111
+ Handle MCP server elicitation requests (e.g. OAuth flows, form input):
1112
+
1113
+ ```ruby
1114
+ options = ClaudeAgent::Options.new(
1115
+ on_elicitation: ->(request, signal:) {
1116
+ # request contains: server_name, message, mode, url, elicitation_id, requested_schema
1117
+ case request[:mode]
1118
+ when "oauth"
1119
+ # Handle OAuth flow
1120
+ { action: "accept", content: { token: "..." } }
1121
+ else
1122
+ { action: "decline" }
1123
+ end
1124
+ }
1125
+ )
1126
+ ```
1127
+
1128
+ Without `on_elicitation`, all elicitation requests are declined by default.
1129
+
1087
1130
  ## Error Handling
1088
1131
 
1089
1132
  The SDK provides specific error types:
@@ -1213,6 +1256,7 @@ client.cumulative_usage # CumulativeUsage with totals across all turns
1213
1256
  # Query capabilities
1214
1257
  client.supported_commands.each { |cmd| puts "#{cmd.name}: #{cmd.description}" }
1215
1258
  client.supported_models.each { |model| puts "#{model.value}: #{model.display_name}" }
1259
+ client.supported_agents.each { |agent| puts "#{agent.name}: #{agent.description}" }
1216
1260
  client.mcp_server_status.each { |s| puts "#{s.name}: #{s.status}" }
1217
1261
  puts client.account_info.email
1218
1262
 
@@ -1391,7 +1435,7 @@ session = ClaudeAgent.unstable_v2_create_session(options)
1391
1435
  | Type | Purpose |
1392
1436
  |--------------------------|----------------------------------------------------------------------------------|
1393
1437
  | `TurnResult` | Complete agent turn with text, tools, usage, and status accessors |
1394
- | `ToolActivity` | Tool use/result pair with turn index and timing (immutable, post-turn) |
1438
+ | `ToolActivity` | Tool use/result pair with turn index and timing (immutable, post-turn) |
1395
1439
  | `LiveToolActivity` | Mutable real-time tool status (running/done/error) with elapsed time |
1396
1440
  | `ToolActivityTracker` | Enumerable collection of `LiveToolActivity` with auto-wiring and `on_change` |
1397
1441
  | `CumulativeUsage` | Running totals of tokens, cost, turns, and duration |
@@ -1400,6 +1444,7 @@ session = ClaudeAgent.unstable_v2_create_session(options)
1400
1444
  | `EventHandler` | Typed event callback registry |
1401
1445
  | `SlashCommand` | Available slash commands (name, description, argument_hint) |
1402
1446
  | `ModelInfo` | Available models (value, display_name, description) |
1447
+ | `AgentInfo` | Available agents (name, description, model) |
1403
1448
  | `McpServerStatus` | MCP server status (name, status, server_info) |
1404
1449
  | `AccountInfo` | Account information (email, organization, subscription_type) |
1405
1450
  | `ModelUsage` | Per-model usage stats (input_tokens, output_tokens, cost_usd) |
data/Rakefile CHANGED
@@ -35,6 +35,23 @@ task :test_integration do
35
35
  Rake::Task[:_integration].invoke
36
36
  end
37
37
 
38
+ # Internal task for running smoke tests
39
+ Minitest::TestTask.create(:_smoke) do |t|
40
+ t.test_globs = [ "test/smoke/**/test_*.rb" ]
41
+ t.warning = false
42
+ end
43
+
44
+ # Smoke tests - wrapper that sets INTEGRATION + SMOKE + Ollama defaults
45
+ desc "Run smoke tests against local LLM (e.g. Ollama)"
46
+ task :test_smoke do
47
+ ENV["INTEGRATION"] = "true"
48
+ ENV["SMOKE"] = "true"
49
+ ENV["ANTHROPIC_BASE_URL"] ||= "http://localhost:11434"
50
+ ENV["ANTHROPIC_API_KEY"] ||= "ollama"
51
+ ENV["ANTHROPIC_AUTH_TOKEN"] ||= "ollama"
52
+ Rake::Task[:_smoke].invoke
53
+ end
54
+
38
55
  require "rubocop/rake_task"
39
56
 
40
57
  RuboCop::RakeTask.new