claude-agent-sdk 0.16.5 → 0.16.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2fe7bf072c2a3b8448b23bf82c95e7f0a896b347bd43039145458b72e21f301
4
- data.tar.gz: 1a58f5e31ef7a872aeaf75a3aada1c8e60a264d9ad5e93615b62c42d96179d99
3
+ metadata.gz: 5e77eea013fead986f47ff000864ddb9e52a3408186cf09bf3bb8d5214edec28
4
+ data.tar.gz: 49f94edca00072ec403cb26eb79165979d8684e655336bce6ca53f3c1a31eb81
5
5
  SHA512:
6
- metadata.gz: e76c6cfe62c7b01320edf7a3d659e44fce46921f764132da7ab0f8223bb1d5a4a2b4457f5406b7bc68a1dde91f7af29fd0d4ff565334ed2cae8ad120339bac83
7
- data.tar.gz: 9e1920a7254f5e27dab4ae88cc5ba6d8ac7430a7ec32e8c9157d80c792a65bee8c258096f52ac20ce69d5bedfd1a629a2c8ef852341c9b03e4aaa631c491d912
6
+ metadata.gz: '08544dd09bb2d8c1127364517e1cc3527aaa0321d8c2d957550f078c5ce1d20796f5622f987ddd9f128377cad697d314a63a31031cd8eeb2e823bbfef282ee17'
7
+ data.tar.gz: e7149a4d3d9546631449d71878a6480d3d8758c2d990b3836cdc1dde65e93d09a8d19658c781c4f2cf5655bcb3c910dac84bc7d0076f635af4957f78a2e91fe9
data/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.16.6] - 2026-04-29
11
+
12
+ ### Added
13
+ - Type classes now accept hash construction with mixed key shapes — symbols or strings, snake_case or camelCase — and support bracket access (`obj[:field]`, `obj['field']`). `UserMessage.new({"sessionId" => "abc"})` works the same as `UserMessage.new(session_id: "abc")`. `Type.from_hash(nil)` and `Type.wrap(nil)` are nil-safe.
14
+ - `ClaudeAgentOptions.new(nil)` is accepted and yields an options object populated with configured defaults.
15
+
16
+ ### Changed
17
+ - Discriminator fields on type classes (`type` on `SystemPromptFile`/`McpStdioServerConfig`/etc., `behavior` on `PermissionResultAllow`/`Deny`, `hook_event_name` on every hook input/output) are now `attr_reader`-only, set authoritatively in `initialize`. They cannot be overwritten externally.
18
+ - `ClaudeAgentOptions` continues to raise `ArgumentError` on unknown keys (constructor, `dup_with`, and `[]=`); other type subclasses silently drop unknown keys for forward-compatibility with newer CLI output.
19
+
20
+ ### Internal
21
+ - Unified ~50 type classes (messages, hook inputs/outputs, MCP configs, system prompt configs, sandbox settings) under a shared `Type` base class. `lib/claude_agent_sdk/types.rb` shrank from 1510 to 588 lines; `MessageParser`'s system-message dispatch went from 215 lines to a 14-entry lookup table.
22
+
10
23
  ## [0.16.5] - 2026-04-24
11
24
 
12
25
  ### Added
data/README.md CHANGED
@@ -108,7 +108,7 @@ Add this line to your application's Gemfile:
108
108
  gem 'claude-agent-sdk', github: 'ya-luotao/claude-agent-sdk-ruby'
109
109
 
110
110
  # Or use a stable version from RubyGems
111
- gem 'claude-agent-sdk', '~> 0.16.5'
111
+ gem 'claude-agent-sdk', '~> 0.16.6'
112
112
  ```
113
113
 
114
114
  And then execute:
@@ -78,214 +78,57 @@ module ClaudeAgentSDK
78
78
  )
79
79
  end
80
80
 
81
+ # Typed SystemMessage subclasses inherit from `Type` and accept the raw
82
+ # CLI hash directly — camelCase and snake_case keys are normalized by the
83
+ # base class, and the full hash is captured as `#data`.
84
+ SYSTEM_MESSAGE_CLASSES = {
85
+ 'init' => InitMessage,
86
+ 'compact_boundary' => CompactBoundaryMessage,
87
+ 'status' => StatusMessage,
88
+ 'api_retry' => APIRetryMessage,
89
+ 'local_command_output' => LocalCommandOutputMessage,
90
+ 'hook_started' => HookStartedMessage,
91
+ 'hook_progress' => HookProgressMessage,
92
+ 'hook_response' => HookResponseMessage,
93
+ 'session_state_changed' => SessionStateChangedMessage,
94
+ 'files_persisted' => FilesPersistedMessage,
95
+ 'elicitation_complete' => ElicitationCompleteMessage,
96
+ 'task_started' => TaskStartedMessage,
97
+ 'task_progress' => TaskProgressMessage,
98
+ 'task_notification' => TaskNotificationMessage
99
+ }.freeze
100
+
81
101
  def self.parse_system_message(data)
82
- case data[:subtype]
83
- when 'init'
84
- InitMessage.new(
85
- subtype: data[:subtype], data: data,
86
- uuid: data[:uuid], session_id: data[:session_id],
87
- agents: data[:agents], api_key_source: data[:apiKeySource] || data[:api_key_source],
88
- betas: data[:betas], claude_code_version: data[:claude_code_version],
89
- cwd: data[:cwd], tools: data[:tools], mcp_servers: data[:mcp_servers],
90
- model: data[:model], permission_mode: data[:permissionMode] || data[:permission_mode],
91
- slash_commands: data[:slash_commands], output_style: data[:output_style],
92
- skills: data[:skills], plugins: data[:plugins],
93
- fast_mode_state: data[:fastModeState] || data[:fast_mode_state]
94
- )
95
- when 'compact_boundary'
96
- raw_metadata = data[:compact_metadata]
97
- CompactBoundaryMessage.new(
98
- subtype: data[:subtype], data: data,
99
- uuid: data[:uuid], session_id: data[:session_id],
100
- compact_metadata: CompactMetadata.from_hash(raw_metadata)
101
- )
102
- when 'status'
103
- StatusMessage.new(
104
- subtype: data[:subtype], data: data,
105
- uuid: data[:uuid], session_id: data[:session_id],
106
- status: data[:status],
107
- permission_mode: data[:permissionMode] || data[:permission_mode]
108
- )
109
- when 'api_retry'
110
- APIRetryMessage.new(
111
- subtype: data[:subtype], data: data,
112
- uuid: data[:uuid], session_id: data[:session_id],
113
- attempt: data[:attempt], max_retries: data[:maxRetries] || data[:max_retries],
114
- retry_delay_ms: data[:retryDelayMs] || data[:retry_delay_ms],
115
- error_status: data[:errorStatus] || data[:error_status],
116
- error: data[:error]
117
- )
118
- when 'local_command_output'
119
- LocalCommandOutputMessage.new(
120
- subtype: data[:subtype], data: data,
121
- uuid: data[:uuid], session_id: data[:session_id],
122
- content: data[:content]
123
- )
124
- when 'hook_started'
125
- HookStartedMessage.new(
126
- subtype: data[:subtype], data: data,
127
- uuid: data[:uuid], session_id: data[:session_id],
128
- hook_id: data[:hookId] || data[:hook_id],
129
- hook_name: data[:hookName] || data[:hook_name],
130
- hook_event: data[:hookEvent] || data[:hook_event]
131
- )
132
- when 'hook_progress'
133
- HookProgressMessage.new(
134
- subtype: data[:subtype], data: data,
135
- uuid: data[:uuid], session_id: data[:session_id],
136
- hook_id: data[:hookId] || data[:hook_id],
137
- hook_name: data[:hookName] || data[:hook_name],
138
- hook_event: data[:hookEvent] || data[:hook_event],
139
- stdout: data[:stdout], stderr: data[:stderr], output: data[:output]
140
- )
141
- when 'hook_response'
142
- HookResponseMessage.new(
143
- subtype: data[:subtype], data: data,
144
- uuid: data[:uuid], session_id: data[:session_id],
145
- hook_id: data[:hookId] || data[:hook_id],
146
- hook_name: data[:hookName] || data[:hook_name],
147
- hook_event: data[:hookEvent] || data[:hook_event],
148
- output: data[:output], stdout: data[:stdout], stderr: data[:stderr],
149
- exit_code: data[:exitCode] || data[:exit_code],
150
- outcome: data[:outcome]
151
- )
152
- when 'session_state_changed'
153
- SessionStateChangedMessage.new(
154
- subtype: data[:subtype], data: data,
155
- uuid: data[:uuid], session_id: data[:session_id],
156
- state: data[:state]
157
- )
158
- when 'files_persisted'
159
- FilesPersistedMessage.new(
160
- subtype: data[:subtype], data: data,
161
- uuid: data[:uuid], session_id: data[:session_id],
162
- files: data[:files], failed: data[:failed],
163
- processed_at: data[:processedAt] || data[:processed_at]
164
- )
165
- when 'elicitation_complete'
166
- ElicitationCompleteMessage.new(
167
- subtype: data[:subtype], data: data,
168
- uuid: data[:uuid], session_id: data[:session_id],
169
- mcp_server_name: data[:mcpServerName] || data[:mcp_server_name],
170
- elicitation_id: data[:elicitationId] || data[:elicitation_id]
171
- )
172
- when 'task_started'
173
- TaskStartedMessage.new(
174
- subtype: data[:subtype], data: data,
175
- task_id: data[:task_id], description: data[:description],
176
- uuid: data[:uuid], session_id: data[:session_id],
177
- tool_use_id: data[:tool_use_id], task_type: data[:task_type],
178
- workflow_name: data[:workflowName] || data[:workflow_name],
179
- prompt: data[:prompt]
180
- )
181
- when 'task_progress'
182
- TaskProgressMessage.new(
183
- subtype: data[:subtype], data: data,
184
- task_id: data[:task_id], description: data[:description],
185
- usage: data[:usage], uuid: data[:uuid], session_id: data[:session_id],
186
- tool_use_id: data[:tool_use_id], last_tool_name: data[:last_tool_name],
187
- summary: data[:summary]
188
- )
189
- when 'task_notification'
190
- TaskNotificationMessage.new(
191
- subtype: data[:subtype], data: data,
192
- task_id: data[:task_id], status: data[:status],
193
- output_file: data[:output_file], summary: data[:summary],
194
- uuid: data[:uuid], session_id: data[:session_id],
195
- tool_use_id: data[:tool_use_id], usage: data[:usage]
196
- )
197
- else
198
- SystemMessage.new(subtype: data[:subtype], data: data)
199
- end
102
+ klass = SYSTEM_MESSAGE_CLASSES[data[:subtype]] || SystemMessage
103
+ klass.new(data)
200
104
  end
201
105
 
202
106
  def self.parse_result_message(data)
203
- ResultMessage.new(
204
- subtype: data[:subtype],
205
- duration_ms: data[:duration_ms],
206
- duration_api_ms: data[:duration_api_ms],
207
- is_error: data[:is_error],
208
- num_turns: data[:num_turns],
209
- session_id: data[:session_id],
210
- stop_reason: data[:stop_reason],
211
- total_cost_usd: data[:total_cost_usd],
212
- usage: data[:usage],
213
- result: data[:result],
214
- structured_output: data[:structured_output],
215
- model_usage: data[:modelUsage] || data[:model_usage],
216
- permission_denials: data[:permission_denials],
217
- errors: data[:errors],
218
- uuid: data[:uuid],
219
- fast_mode_state: data[:fastModeState] || data[:fast_mode_state]
220
- )
107
+ ResultMessage.new(data)
221
108
  end
222
109
 
223
110
  def self.parse_stream_event(data)
224
- StreamEvent.new(
225
- uuid: data[:uuid],
226
- session_id: data[:session_id],
227
- event: data[:event],
228
- parent_tool_use_id: data[:parent_tool_use_id]
229
- )
111
+ StreamEvent.new(data)
230
112
  end
231
113
 
232
114
  def self.parse_rate_limit_event(data)
233
- info = data[:rate_limit_info] || {}
234
- rate_limit_info = RateLimitInfo.new(
235
- status: info[:status],
236
- resets_at: info[:resetsAt],
237
- rate_limit_type: info[:rateLimitType],
238
- utilization: info[:utilization],
239
- overage_status: info[:overageStatus],
240
- overage_resets_at: info[:overageResetsAt],
241
- overage_disabled_reason: info[:overageDisabledReason],
242
- raw: info
243
- )
244
- RateLimitEvent.new(
245
- rate_limit_info: rate_limit_info,
246
- uuid: data[:uuid],
247
- session_id: data[:session_id],
248
- raw_data: data
249
- )
115
+ RateLimitEvent.new(data.merge(raw_data: data))
250
116
  end
251
117
 
252
118
  def self.parse_tool_progress_message(data)
253
- ToolProgressMessage.new(
254
- uuid: data[:uuid],
255
- session_id: data[:session_id],
256
- tool_use_id: data[:toolUseId] || data[:tool_use_id],
257
- tool_name: data[:toolName] || data[:tool_name],
258
- parent_tool_use_id: data[:parentToolUseId] || data[:parent_tool_use_id],
259
- elapsed_time_seconds: data[:elapsedTimeSeconds] || data[:elapsed_time_seconds],
260
- task_id: data[:taskId] || data[:task_id]
261
- )
119
+ ToolProgressMessage.new(data)
262
120
  end
263
121
 
264
122
  def self.parse_auth_status_message(data)
265
- AuthStatusMessage.new(
266
- uuid: data[:uuid],
267
- session_id: data[:session_id],
268
- is_authenticating: data[:isAuthenticating] || data[:is_authenticating],
269
- output: data[:output],
270
- error: data[:error]
271
- )
123
+ AuthStatusMessage.new(data)
272
124
  end
273
125
 
274
126
  def self.parse_tool_use_summary_message(data)
275
- ToolUseSummaryMessage.new(
276
- uuid: data[:uuid],
277
- session_id: data[:session_id],
278
- summary: data[:summary],
279
- preceding_tool_use_ids: data[:precedingToolUseIds] || data[:preceding_tool_use_ids]
280
- )
127
+ ToolUseSummaryMessage.new(data)
281
128
  end
282
129
 
283
130
  def self.parse_prompt_suggestion_message(data)
284
- PromptSuggestionMessage.new(
285
- uuid: data[:uuid],
286
- session_id: data[:session_id],
287
- suggestion: data[:suggestion]
288
- )
131
+ PromptSuggestionMessage.new(data)
289
132
  end
290
133
 
291
134
  # Accepts blocks with either symbol or string keys — live CLI messages