anthropic 1.48.2 → 1.49.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.
Files changed (212) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +1 -1
  4. data/lib/anthropic/client.rb +8 -2
  5. data/lib/anthropic/errors.rb +14 -0
  6. data/lib/anthropic/helpers/aws/client.rb +3 -3
  7. data/lib/anthropic/helpers/aws_auth.rb +39 -29
  8. data/lib/anthropic/helpers/bedrock/client.rb +99 -67
  9. data/lib/anthropic/helpers/bedrock/event_stream.rb +92 -0
  10. data/lib/anthropic/helpers/bedrock/mantle_client.rb +3 -3
  11. data/lib/anthropic/helpers/vertex/client.rb +99 -55
  12. data/lib/anthropic/internal/transport/base_client.rb +196 -33
  13. data/lib/anthropic/internal/util.rb +37 -1
  14. data/lib/anthropic/middleware.rb +435 -0
  15. data/lib/anthropic/models/beta/beta_advisor_tool_20260301.rb +3 -1
  16. data/lib/anthropic/models/beta/beta_code_execution_tool_20250522.rb +3 -1
  17. data/lib/anthropic/models/beta/beta_code_execution_tool_20250825.rb +3 -1
  18. data/lib/anthropic/models/beta/beta_code_execution_tool_20260120.rb +3 -1
  19. data/lib/anthropic/models/beta/beta_code_execution_tool_20260521.rb +86 -0
  20. data/lib/anthropic/models/beta/beta_content_block.rb +3 -3
  21. data/lib/anthropic/models/beta/beta_content_block_param.rb +10 -12
  22. data/lib/anthropic/models/beta/beta_fallback_block.rb +13 -5
  23. data/lib/anthropic/models/beta/beta_fallback_block_param.rb +22 -13
  24. data/lib/anthropic/models/beta/beta_fallback_refusal_trigger.rb +44 -0
  25. data/lib/anthropic/models/beta/beta_memory_tool_20250818.rb +3 -1
  26. data/lib/anthropic/models/beta/beta_raw_content_block_start_event.rb +3 -3
  27. data/lib/anthropic/models/beta/beta_refusal_stop_details.rb +3 -7
  28. data/lib/anthropic/models/beta/beta_tool.rb +3 -1
  29. data/lib/anthropic/models/beta/beta_tool_bash_20241022.rb +3 -1
  30. data/lib/anthropic/models/beta/beta_tool_bash_20250124.rb +3 -1
  31. data/lib/anthropic/models/beta/beta_tool_computer_use_20241022.rb +3 -1
  32. data/lib/anthropic/models/beta/beta_tool_computer_use_20250124.rb +3 -1
  33. data/lib/anthropic/models/beta/beta_tool_computer_use_20251124.rb +3 -1
  34. data/lib/anthropic/models/beta/beta_tool_search_tool_bm25_20251119.rb +3 -1
  35. data/lib/anthropic/models/beta/beta_tool_search_tool_regex_20251119.rb +3 -1
  36. data/lib/anthropic/models/beta/beta_tool_text_editor_20241022.rb +3 -1
  37. data/lib/anthropic/models/beta/beta_tool_text_editor_20250124.rb +3 -1
  38. data/lib/anthropic/models/beta/beta_tool_text_editor_20250429.rb +3 -1
  39. data/lib/anthropic/models/beta/beta_tool_text_editor_20250728.rb +3 -1
  40. data/lib/anthropic/models/beta/beta_tool_union.rb +4 -1
  41. data/lib/anthropic/models/beta/beta_web_fetch_tool_20250910.rb +3 -1
  42. data/lib/anthropic/models/beta/beta_web_fetch_tool_20260209.rb +3 -1
  43. data/lib/anthropic/models/beta/beta_web_fetch_tool_20260309.rb +3 -1
  44. data/lib/anthropic/models/beta/beta_web_search_tool_20250305.rb +3 -1
  45. data/lib/anthropic/models/beta/beta_web_search_tool_20260209.rb +3 -1
  46. data/lib/anthropic/models/beta/beta_webhook_event.rb +2 -2
  47. data/lib/anthropic/models/beta/beta_webhook_event_data.rb +3 -1
  48. data/lib/anthropic/models/beta/beta_webhook_session_updated_event_data.rb +41 -0
  49. data/lib/anthropic/models/beta/message_count_tokens_params.rb +6 -3
  50. data/lib/anthropic/models/beta/message_create_params.rb +2 -2
  51. data/lib/anthropic/models/beta/messages/batch_create_params.rb +2 -2
  52. data/lib/anthropic/models/beta/unwrap_webhook_event.rb +2 -2
  53. data/lib/anthropic/models/code_execution_tool_20250522.rb +3 -1
  54. data/lib/anthropic/models/code_execution_tool_20250825.rb +3 -1
  55. data/lib/anthropic/models/code_execution_tool_20260120.rb +3 -1
  56. data/lib/anthropic/models/code_execution_tool_20260521.rb +82 -0
  57. data/lib/anthropic/models/memory_tool_20250818.rb +3 -1
  58. data/lib/anthropic/models/message_count_tokens_params.rb +2 -2
  59. data/lib/anthropic/models/message_count_tokens_tool.rb +4 -1
  60. data/lib/anthropic/models/message_create_params.rb +2 -2
  61. data/lib/anthropic/models/messages/batch_create_params.rb +2 -2
  62. data/lib/anthropic/models/refusal_stop_details.rb +3 -7
  63. data/lib/anthropic/models/tool.rb +3 -1
  64. data/lib/anthropic/models/tool_bash_20250124.rb +3 -1
  65. data/lib/anthropic/models/tool_search_tool_bm25_20251119.rb +3 -1
  66. data/lib/anthropic/models/tool_search_tool_regex_20251119.rb +3 -1
  67. data/lib/anthropic/models/tool_text_editor_20250124.rb +3 -1
  68. data/lib/anthropic/models/tool_text_editor_20250429.rb +3 -1
  69. data/lib/anthropic/models/tool_text_editor_20250728.rb +3 -1
  70. data/lib/anthropic/models/tool_union.rb +4 -1
  71. data/lib/anthropic/models/web_fetch_tool_20250910.rb +3 -1
  72. data/lib/anthropic/models/web_fetch_tool_20260209.rb +3 -1
  73. data/lib/anthropic/models/web_fetch_tool_20260309.rb +3 -1
  74. data/lib/anthropic/models/web_search_tool_20250305.rb +3 -1
  75. data/lib/anthropic/models/web_search_tool_20260209.rb +3 -1
  76. data/lib/anthropic/models.rb +2 -0
  77. data/lib/anthropic/request_options.rb +9 -0
  78. data/lib/anthropic/resources/beta/messages.rb +3 -3
  79. data/lib/anthropic/resources/messages.rb +3 -3
  80. data/lib/anthropic/version.rb +1 -1
  81. data/lib/anthropic.rb +6 -0
  82. data/rbi/anthropic/client.rbi +7 -2
  83. data/rbi/anthropic/errors.rbi +5 -0
  84. data/rbi/anthropic/helpers/aws/client.rbi +3 -6
  85. data/rbi/anthropic/helpers/bedrock/client.rbi +24 -13
  86. data/rbi/anthropic/helpers/bedrock/event_stream.rbi +25 -0
  87. data/rbi/anthropic/helpers/bedrock/mantle_client.rbi +3 -6
  88. data/rbi/anthropic/helpers/vertex/client.rbi +28 -8
  89. data/rbi/anthropic/internal/transport/base_client.rbi +39 -4
  90. data/rbi/anthropic/internal/util.rbi +5 -0
  91. data/rbi/anthropic/middleware.rbi +338 -0
  92. data/rbi/anthropic/models/beta/beta_advisor_tool_20260301.rbi +7 -1
  93. data/rbi/anthropic/models/beta/beta_code_execution_tool_20250522.rbi +7 -1
  94. data/rbi/anthropic/models/beta/beta_code_execution_tool_20250825.rbi +7 -1
  95. data/rbi/anthropic/models/beta/beta_code_execution_tool_20260120.rbi +7 -1
  96. data/rbi/anthropic/models/beta/beta_code_execution_tool_20260521.rbi +178 -0
  97. data/rbi/anthropic/models/beta/beta_fallback_block.rbi +19 -4
  98. data/rbi/anthropic/models/beta/beta_fallback_block_param.rbi +23 -13
  99. data/rbi/anthropic/models/beta/beta_fallback_refusal_trigger.rbi +108 -0
  100. data/rbi/anthropic/models/beta/beta_memory_tool_20250818.rbi +7 -1
  101. data/rbi/anthropic/models/beta/beta_refusal_stop_details.rbi +3 -9
  102. data/rbi/anthropic/models/beta/beta_tool.rbi +7 -1
  103. data/rbi/anthropic/models/beta/beta_tool_bash_20241022.rbi +7 -1
  104. data/rbi/anthropic/models/beta/beta_tool_bash_20250124.rbi +7 -1
  105. data/rbi/anthropic/models/beta/beta_tool_computer_use_20241022.rbi +7 -1
  106. data/rbi/anthropic/models/beta/beta_tool_computer_use_20250124.rbi +7 -1
  107. data/rbi/anthropic/models/beta/beta_tool_computer_use_20251124.rbi +7 -1
  108. data/rbi/anthropic/models/beta/beta_tool_search_tool_bm25_20251119.rbi +7 -1
  109. data/rbi/anthropic/models/beta/beta_tool_search_tool_regex_20251119.rbi +7 -1
  110. data/rbi/anthropic/models/beta/beta_tool_text_editor_20241022.rbi +7 -1
  111. data/rbi/anthropic/models/beta/beta_tool_text_editor_20250124.rbi +7 -1
  112. data/rbi/anthropic/models/beta/beta_tool_text_editor_20250429.rbi +7 -1
  113. data/rbi/anthropic/models/beta/beta_tool_text_editor_20250728.rbi +7 -1
  114. data/rbi/anthropic/models/beta/beta_tool_union.rbi +1 -0
  115. data/rbi/anthropic/models/beta/beta_web_fetch_tool_20250910.rbi +7 -1
  116. data/rbi/anthropic/models/beta/beta_web_fetch_tool_20260209.rbi +7 -1
  117. data/rbi/anthropic/models/beta/beta_web_fetch_tool_20260309.rbi +7 -1
  118. data/rbi/anthropic/models/beta/beta_web_search_tool_20250305.rbi +7 -1
  119. data/rbi/anthropic/models/beta/beta_web_search_tool_20260209.rbi +7 -1
  120. data/rbi/anthropic/models/beta/beta_webhook_event.rbi +6 -3
  121. data/rbi/anthropic/models/beta/beta_webhook_event_data.rbi +2 -1
  122. data/rbi/anthropic/models/beta/beta_webhook_session_updated_event_data.rbi +63 -0
  123. data/rbi/anthropic/models/beta/message_count_tokens_params.rbi +5 -0
  124. data/rbi/anthropic/models/beta/message_create_params.rbi +4 -0
  125. data/rbi/anthropic/models/beta/messages/batch_create_params.rbi +4 -0
  126. data/rbi/anthropic/models/beta/unwrap_webhook_event.rbi +2 -1
  127. data/rbi/anthropic/models/code_execution_tool_20250522.rbi +7 -1
  128. data/rbi/anthropic/models/code_execution_tool_20250825.rbi +7 -1
  129. data/rbi/anthropic/models/code_execution_tool_20260120.rbi +7 -1
  130. data/rbi/anthropic/models/code_execution_tool_20260521.rbi +168 -0
  131. data/rbi/anthropic/models/memory_tool_20250818.rbi +7 -1
  132. data/rbi/anthropic/models/message_count_tokens_params.rbi +4 -0
  133. data/rbi/anthropic/models/message_count_tokens_tool.rbi +1 -0
  134. data/rbi/anthropic/models/message_create_params.rbi +4 -0
  135. data/rbi/anthropic/models/messages/batch_create_params.rbi +4 -0
  136. data/rbi/anthropic/models/refusal_stop_details.rbi +3 -9
  137. data/rbi/anthropic/models/tool.rbi +7 -1
  138. data/rbi/anthropic/models/tool_bash_20250124.rbi +7 -1
  139. data/rbi/anthropic/models/tool_search_tool_bm25_20251119.rbi +7 -1
  140. data/rbi/anthropic/models/tool_search_tool_regex_20251119.rbi +7 -1
  141. data/rbi/anthropic/models/tool_text_editor_20250124.rbi +7 -1
  142. data/rbi/anthropic/models/tool_text_editor_20250429.rbi +7 -1
  143. data/rbi/anthropic/models/tool_text_editor_20250728.rbi +7 -1
  144. data/rbi/anthropic/models/tool_union.rbi +1 -0
  145. data/rbi/anthropic/models/web_fetch_tool_20250910.rbi +7 -1
  146. data/rbi/anthropic/models/web_fetch_tool_20260209.rbi +7 -1
  147. data/rbi/anthropic/models/web_fetch_tool_20260309.rbi +7 -1
  148. data/rbi/anthropic/models/web_search_tool_20250305.rbi +7 -1
  149. data/rbi/anthropic/models/web_search_tool_20260209.rbi +7 -1
  150. data/rbi/anthropic/models.rbi +2 -0
  151. data/rbi/anthropic/request_options.rbi +5 -0
  152. data/rbi/anthropic/resources/beta/messages.rbi +3 -0
  153. data/rbi/anthropic/resources/messages.rbi +3 -0
  154. data/sig/anthropic/client.rbs +2 -1
  155. data/sig/anthropic/errors.rbs +3 -0
  156. data/sig/anthropic/helpers/bedrock/client.rbs +12 -4
  157. data/sig/anthropic/helpers/vertex/client.rbs +17 -4
  158. data/sig/anthropic/internal/transport/base_client.rbs +18 -3
  159. data/sig/anthropic/internal/util.rbs +2 -0
  160. data/sig/anthropic/middleware.rbs +117 -0
  161. data/sig/anthropic/models/beta/beta_advisor_tool_20260301.rbs +5 -1
  162. data/sig/anthropic/models/beta/beta_code_execution_tool_20250522.rbs +5 -1
  163. data/sig/anthropic/models/beta/beta_code_execution_tool_20250825.rbs +5 -1
  164. data/sig/anthropic/models/beta/beta_code_execution_tool_20260120.rbs +5 -1
  165. data/sig/anthropic/models/beta/beta_code_execution_tool_20260521.rbs +74 -0
  166. data/sig/anthropic/models/beta/beta_fallback_block.rbs +5 -0
  167. data/sig/anthropic/models/beta/beta_fallback_block_param.rbs +9 -2
  168. data/sig/anthropic/models/beta/beta_fallback_refusal_trigger.rbs +42 -0
  169. data/sig/anthropic/models/beta/beta_memory_tool_20250818.rbs +5 -1
  170. data/sig/anthropic/models/beta/beta_tool.rbs +5 -1
  171. data/sig/anthropic/models/beta/beta_tool_bash_20241022.rbs +5 -1
  172. data/sig/anthropic/models/beta/beta_tool_bash_20250124.rbs +5 -1
  173. data/sig/anthropic/models/beta/beta_tool_computer_use_20241022.rbs +5 -1
  174. data/sig/anthropic/models/beta/beta_tool_computer_use_20250124.rbs +5 -1
  175. data/sig/anthropic/models/beta/beta_tool_computer_use_20251124.rbs +5 -1
  176. data/sig/anthropic/models/beta/beta_tool_search_tool_bm25_20251119.rbs +5 -1
  177. data/sig/anthropic/models/beta/beta_tool_search_tool_regex_20251119.rbs +5 -1
  178. data/sig/anthropic/models/beta/beta_tool_text_editor_20241022.rbs +5 -1
  179. data/sig/anthropic/models/beta/beta_tool_text_editor_20250124.rbs +5 -1
  180. data/sig/anthropic/models/beta/beta_tool_text_editor_20250429.rbs +5 -1
  181. data/sig/anthropic/models/beta/beta_tool_text_editor_20250728.rbs +5 -1
  182. data/sig/anthropic/models/beta/beta_tool_union.rbs +1 -0
  183. data/sig/anthropic/models/beta/beta_web_fetch_tool_20250910.rbs +5 -1
  184. data/sig/anthropic/models/beta/beta_web_fetch_tool_20260209.rbs +5 -1
  185. data/sig/anthropic/models/beta/beta_web_fetch_tool_20260309.rbs +5 -1
  186. data/sig/anthropic/models/beta/beta_web_search_tool_20250305.rbs +5 -1
  187. data/sig/anthropic/models/beta/beta_web_search_tool_20260209.rbs +5 -1
  188. data/sig/anthropic/models/beta/beta_webhook_event_data.rbs +1 -0
  189. data/sig/anthropic/models/beta/beta_webhook_session_updated_event_data.rbs +39 -0
  190. data/sig/anthropic/models/beta/message_count_tokens_params.rbs +1 -0
  191. data/sig/anthropic/models/code_execution_tool_20250522.rbs +5 -1
  192. data/sig/anthropic/models/code_execution_tool_20250825.rbs +5 -1
  193. data/sig/anthropic/models/code_execution_tool_20260120.rbs +5 -1
  194. data/sig/anthropic/models/code_execution_tool_20260521.rbs +70 -0
  195. data/sig/anthropic/models/memory_tool_20250818.rbs +5 -1
  196. data/sig/anthropic/models/message_count_tokens_tool.rbs +1 -0
  197. data/sig/anthropic/models/tool.rbs +5 -1
  198. data/sig/anthropic/models/tool_bash_20250124.rbs +5 -1
  199. data/sig/anthropic/models/tool_search_tool_bm25_20251119.rbs +5 -1
  200. data/sig/anthropic/models/tool_search_tool_regex_20251119.rbs +5 -1
  201. data/sig/anthropic/models/tool_text_editor_20250124.rbs +5 -1
  202. data/sig/anthropic/models/tool_text_editor_20250429.rbs +5 -1
  203. data/sig/anthropic/models/tool_text_editor_20250728.rbs +5 -1
  204. data/sig/anthropic/models/tool_union.rbs +1 -0
  205. data/sig/anthropic/models/web_fetch_tool_20250910.rbs +5 -1
  206. data/sig/anthropic/models/web_fetch_tool_20260209.rbs +5 -1
  207. data/sig/anthropic/models/web_fetch_tool_20260309.rbs +5 -1
  208. data/sig/anthropic/models/web_search_tool_20250305.rbs +5 -1
  209. data/sig/anthropic/models/web_search_tool_20260209.rbs +5 -1
  210. data/sig/anthropic/models.rbs +2 -0
  211. data/sig/anthropic/request_options.rbs +4 -1
  212. metadata +19 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 04770f1a0fcb24be2c40a0a67b9397c0103218ff9c082e75d9659c0673ff5aac
4
- data.tar.gz: df85394d335c2666281c5c3d05af57465480712df8664a7885721481406e81cc
3
+ metadata.gz: 45df9830dd4224180e2702068ad79b01ad5f045a6499969bbddfc5a49f29cec9
4
+ data.tar.gz: f501463bdb25da9354140ada09b8c638064fcf1c57ae751e9156c526b5281acb
5
5
  SHA512:
6
- metadata.gz: 8940514153f62cefb0878d36dc786f542a0002e38543c3e52dc88d9d32e34f36f99db5c15e4de894a84c39ff361f03711b6a88613dee198343ede2e337bccba2
7
- data.tar.gz: f6c21933bb2e77445eff2925250b25315f729c8ae81f45e7a0be304959996d75e19a6398539c4a063380b3376f556b35ac1a1ad16d5ca9f03074e74c642ef939
6
+ metadata.gz: d4521d3d6191009cd99cd92898d6cfdca44ae99908d06ddaafee22665f21ba4751c6c02c5fb705ddea04827251769b69c53e62734e9edb04f60f9aa7dffe6802
7
+ data.tar.gz: 7d917f565b12839a5755d0e9638d2d57a05a489bd022d3284f2ba4bd812d488f5b2b635e38f8a3ad12fb24f8c305a7dcb16aed77d9c7ca17f66386183e607c39
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.49.0 (2026-06-18)
4
+
5
+ Full Changelog: [v1.48.2...v1.49.0](https://github.com/anthropics/anthropic-sdk-ruby/compare/v1.48.2...v1.49.0)
6
+
7
+ ### Features
8
+
9
+ * **api:** add support for new code_execution_20260120 tool ([9e4d388](https://github.com/anthropics/anthropic-sdk-ruby/commit/9e4d388649bb020e66ebac13ae4ad90450a3f293))
10
+ * **client:** add HTTP middleware ([#30](https://github.com/anthropics/anthropic-sdk-ruby/issues/30)) ([e6c7245](https://github.com/anthropics/anthropic-sdk-ruby/commit/e6c7245b1539a08d7ec5ad103f0ee291c2bbcb52))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **bedrock:** decode AWS event-stream framing on streaming responses ([2f8d31c](https://github.com/anthropics/anthropic-sdk-ruby/commit/2f8d31cff8ea7119b6271e926c83733bf8297106))
16
+
3
17
  ## 1.48.2 (2026-06-15)
4
18
 
5
19
  Full Changelog: [v1.48.1...v1.48.2](https://github.com/anthropics/anthropic-sdk-ruby/compare/v1.48.1...v1.48.2)
data/README.md CHANGED
@@ -15,7 +15,7 @@ Add to your application's Gemfile:
15
15
  <!-- x-release-please-start-version -->
16
16
 
17
17
  ```ruby
18
- gem "anthropic", "~> 1.48.2"
18
+ gem "anthropic", "~> 1.49.0"
19
19
  ```
20
20
 
21
21
  <!-- x-release-please-end -->
@@ -175,6 +175,10 @@ module Anthropic
175
175
  # @param initial_retry_delay [Float]
176
176
  #
177
177
  # @param max_retry_delay [Float]
178
+ #
179
+ # @param middleware [Array<#call>, #call, nil] Per-attempt HTTP around-middleware. Each
180
+ # entry is a `#call(req, nxt) -> Anthropic::APIResponse` callable. See
181
+ # {Anthropic::Middleware}.
178
182
  def initialize(
179
183
  api_key: nil,
180
184
  auth_token: nil,
@@ -185,7 +189,8 @@ module Anthropic
185
189
  max_retries: self.class::DEFAULT_MAX_RETRIES,
186
190
  timeout: self.class::DEFAULT_TIMEOUT_IN_SECONDS,
187
191
  initial_retry_delay: self.class::DEFAULT_INITIAL_RETRY_DELAY,
188
- max_retry_delay: self.class::DEFAULT_MAX_RETRY_DELAY
192
+ max_retry_delay: self.class::DEFAULT_MAX_RETRY_DELAY,
193
+ middleware: nil
189
194
  )
190
195
  if config && credentials
191
196
  raise ArgumentError, "Pass at most one of `credentials:` or `config:`."
@@ -265,7 +270,8 @@ module Anthropic
265
270
  max_retries: max_retries,
266
271
  initial_retry_delay: initial_retry_delay,
267
272
  max_retry_delay: max_retry_delay,
268
- headers: headers
273
+ headers: headers,
274
+ middleware: middleware
269
275
  )
270
276
 
271
277
  @completions = Anthropic::Resources::Completions.new(client: self)
@@ -8,6 +8,20 @@ module Anthropic
8
8
  # @return [StandardError, nil]
9
9
  end
10
10
 
11
+ # Raise from a middleware to explicitly opt a middleware-origin failure
12
+ # into the SDK's retry loop. Retry classification walks `Exception#cause`,
13
+ # so a middleware may wrap with its own error class and still have the SDK
14
+ # retry as long as `RetryableError` is reachable via the cause chain.
15
+ #
16
+ # @example
17
+ # begin
18
+ # backend.call
19
+ # rescue BackendUnavailable => e
20
+ # raise Anthropic::Errors::RetryableError, cause: e
21
+ # end
22
+ class RetryableError < Anthropic::Errors::Error
23
+ end
24
+
11
25
  class ConversionError < Anthropic::Errors::Error
12
26
  # @return [StandardError, nil]
13
27
  def cause = @cause.nil? ? super : @cause
@@ -108,9 +108,9 @@ module Anthropic
108
108
  end
109
109
 
110
110
  # @api private
111
- private def transform_request(request)
112
- aws_auth_transform_request(request)
113
- end
111
+ #
112
+ # @return [#call]
113
+ private def provider_middleware = method(:aws_auth_provider)
114
114
  end
115
115
  end
116
116
  end
@@ -147,42 +147,52 @@ module Anthropic
147
147
  super
148
148
  end
149
149
 
150
- # Applies workspace-id header and SigV4 signing to the request.
151
- # Call from the including class's `transform_request`.
150
+ # The AWS provider middleware entry: applies the workspace-id header and
151
+ # SigV4 signing per attempt. Return from the including class's
152
+ # `provider_middleware`. Pure — requests are reused across retry
153
+ # attempts, so the incoming `req` is never mutated.
152
154
  #
153
- # @param request [Hash{Symbol=>Object}]
154
- # @return [Hash{Symbol=>Object}]
155
- private def aws_auth_transform_request(request)
156
- headers = request.fetch(:headers)
157
-
158
- headers["anthropic-workspace-id"] = @workspace_id if @workspace_id
159
-
160
- return request unless @use_sig_v4
161
-
162
- # `Aws::Sigv4::Signer#sign_request` only accepts `String` / `IO` / `nil`
163
- # bodies. Multipart and JSONL requests carry a lazy `Enumerable` body
164
- # (see `Util.encode_content`), which the signer rejects. Materialize it
165
- # to a `String` and replace the request body so the signed payload is
166
- # exactly the bytes that go over the wire (the lazy enumerator can only
167
- # be consumed once).
168
- body = request[:body]
155
+ # @param req [Anthropic::APIRequest]
156
+ # @param nxt [#call]
157
+ # @return [Anthropic::APIResponse]
158
+ private def aws_auth_provider(req, nxt)
159
+ if @workspace_id
160
+ req = req.with(headers: {**req.headers, "anthropic-workspace-id" => @workspace_id})
161
+ end
162
+
163
+ # `follow_redirect` stripped `authorization` for a cross-origin hop —
164
+ # don't re-sign and leak credentials to the new origin.
165
+ req = sign_aws_request(req) if @use_sig_v4 && !req.metadata[:cross_origin_redirect]
166
+ nxt.call(req)
167
+ end
168
+
169
+ # SigV4 signs over the body bytes, so the canonical body is encoded here
170
+ # and the signed bytes ride down to the transport. `Aws::Sigv4::Signer`
171
+ # only accepts `String` / `IO` / `nil` bodies, and multipart/JSONL
172
+ # encodings are lazy single-consumer enumerables — materialize those into
173
+ # a `StringIO`, which the terminal's encoding passes through untouched,
174
+ # so the signed payload is exactly the bytes that go over the wire.
175
+ #
176
+ # @param req [Anthropic::APIRequest]
177
+ # @return [Anthropic::APIRequest]
178
+ private def sign_aws_request(req)
179
+ headers, encoded = Anthropic::Internal::Util.encode_content(req.headers, req.body)
169
180
  body =
170
- case body
171
- when nil, String, IO, StringIO
172
- body
173
- when Enumerable
174
- materialized = body.to_a.join
175
- request = {**request, body: materialized}
176
- materialized
181
+ case encoded
182
+ in nil | StringIO | IO
183
+ encoded
184
+ in String
185
+ StringIO.new(encoded)
186
+ in Enumerable
187
+ StringIO.new(encoded.to_a.join)
177
188
  else
178
- body.to_s
189
+ StringIO.new(encoded.to_s)
179
190
  end
180
191
 
181
- sliced = {http_method: request.fetch(:method), url: request.fetch(:url), body: body}
182
- signed = @signer.sign_request({**sliced, headers: headers})
192
+ signed = @signer.sign_request(http_method: req.method, url: req.url, body: body, headers: headers)
183
193
  headers = Anthropic::Internal::Util.normalized_headers(headers, signed.headers)
184
194
  headers.delete("connection")
185
- {**request, headers: headers}
195
+ req.with(headers: headers, body: body)
186
196
  end
187
197
 
188
198
  # Resolves AWS credentials from explicit args, profile, env vars, or default chain.
@@ -4,6 +4,8 @@ module Anthropic
4
4
  module Helpers
5
5
  module Bedrock
6
6
  class Client < Anthropic::Client
7
+ include Anthropic::Helpers::AWSAuth
8
+
7
9
  DEFAULT_VERSION = "bedrock-2023-05-31"
8
10
 
9
11
  # @return [Anthropic::Resources::Messages]
@@ -53,6 +55,11 @@ module Anthropic
53
55
  #
54
56
  # @param max_retry_delay [Float] The maximum number of seconds to wait before retrying a request
55
57
  #
58
+ # @param middleware [Array<#call>, #call, nil] Per-attempt HTTP around-middleware. See
59
+ # {Anthropic::Middleware}. Middleware sees the canonical Anthropic request shape;
60
+ # the Bedrock URL rewrite and SigV4 signing happen inside the continuation, per
61
+ # attempt.
62
+ #
56
63
  def initialize( # rubocop:disable Lint/MissingSuper
57
64
  aws_region: nil,
58
65
  base_url: nil,
@@ -64,7 +71,8 @@ module Anthropic
64
71
  aws_secret_key: nil,
65
72
  aws_session_token: nil,
66
73
  aws_profile: nil,
67
- api_key: nil
74
+ api_key: nil,
75
+ middleware: nil
68
76
  )
69
77
  api_key ||= ENV["AWS_BEARER_TOKEN_BEDROCK"]
70
78
 
@@ -122,6 +130,10 @@ module Anthropic
122
130
  @auth_token = @signer ? nil : api_key
123
131
  @credentials = nil
124
132
  @token_cache = nil
133
+ # For AWSAuth#auth_headers: suppress key headers in SigV4 mode; in
134
+ # API-key mode fall through to the base bearer-token handling.
135
+ @use_sig_v4 = !@signer.nil?
136
+ @use_bearer_auth = false
125
137
 
126
138
  # Skip Anthropic::Client#initialize and bind BaseClient#initialize directly:
127
139
  # the parent's initializer runs OIDC/credential-provider resolution that does
@@ -132,7 +144,8 @@ module Anthropic
132
144
  max_retries: max_retries,
133
145
  initial_retry_delay: initial_retry_delay,
134
146
  max_retry_delay: max_retry_delay,
135
- headers: {"anthropic-version" => "2023-06-01"}
147
+ headers: {"anthropic-version" => "2023-06-01"},
148
+ middleware: middleware
136
149
  )
137
150
 
138
151
  @messages = Anthropic::Resources::Messages.new(client: self)
@@ -178,36 +191,51 @@ module Anthropic
178
191
  #
179
192
  # @return [Hash{Symbol=>Object}]
180
193
  private def build_request(req, opts)
181
- fit_req_to_bedrock_specs!(req)
182
- req = super
183
- body = req.fetch(:body)
184
- req[:body] = StringIO.new(body.to_a.join) if body.is_a?(Enumerator)
185
- req
194
+ validate_bedrock_request!(req)
195
+ super
186
196
  end
187
197
 
188
198
  # @api private
189
199
  #
190
- # Very private API, do not use
191
- #
192
- # @param request [Hash{Symbol=>Object}] .
193
- #
194
- # @option request [Symbol] :method
195
- #
196
- # @option request [URI::Generic] :url
197
- #
198
- # @option request [Hash{String=>String}] :headers
199
- #
200
- # @option request [Object] :body
201
- #
202
- # @return [Hash{Symbol, Object}]
203
- private def transform_request(request)
204
- return request if @auth_token
200
+ # The Bedrock provider middleware: rewrites the canonical request into
201
+ # Bedrock's shape and SigV4-signs it. Appended innermost on every
202
+ # dispatch (below user middleware) and runs per attempt, so each retry
203
+ # or middleware-re-issued leg is re-adapted and re-signed for its own
204
+ # model and URL.
205
+ #
206
+ # @return [#call]
207
+ private def provider_middleware
208
+ lambda do |req, nxt|
209
+ req = adapt_request(req)
210
+ # `follow_redirect` stripped `authorization` for a cross-origin
211
+ # hop — don't re-sign and leak credentials to the new origin.
212
+ req = sign_aws_request(req) if @signer && !req.metadata[:cross_origin_redirect]
213
+ res = nxt.call(req)
214
+ # `invoke-with-response-stream` returns AWS event-stream framing,
215
+ # not SSE — transcode so `Util.decode_sse` and the streaming
216
+ # helpers work unchanged.
217
+ res = adapt_stream_response(res) if EventStream::AWS_CONTENT_TYPE.match?(res.headers["content-type"].to_s)
218
+ res
219
+ end
220
+ end
205
221
 
206
- headers = request.fetch(:headers)
207
- sliced = super.slice(:method, :url, :body).transform_keys(method: :http_method)
208
- signed = @signer.sign_request({**sliced, headers: headers})
209
- headers = Anthropic::Internal::Util.normalized_headers(headers, signed.headers)
210
- {**request, headers: headers}
222
+ # @api private
223
+ #
224
+ # Rebuilds the response with the body transcoded from AWS event-stream
225
+ # framing to SSE bytes and the `content-type` rewritten so
226
+ # {Anthropic::Internal::Util.decode_content} dispatches to `decode_sse`.
227
+ #
228
+ # @param res [Anthropic::APIResponse]
229
+ # @return [Anthropic::APIResponse]
230
+ private def adapt_stream_response(res)
231
+ Anthropic::APIResponse.new(
232
+ status: res.status,
233
+ headers: res.headers.merge("content-type" => "text/event-stream"),
234
+ body: EventStream.to_sse(res.body),
235
+ raw: res.raw,
236
+ streaming: true,
237
+ request: res.request
238
+ )
211
239
  end
212
240
 
213
241
  # @param aws_region [String, nil]
@@ -243,35 +271,14 @@ module Anthropic
243
271
 
244
272
  # @private
245
273
  #
246
- # Overrides request components for Bedrock-specific request-shape requirements.
247
- #
248
- # @param request_components [Hash{Symbol=>Object}] .
249
- #
250
- # @option request_components [Symbol] :method
251
- #
252
- # @option request_components [String, Array<String>] :path
253
- #
254
- # @option request_components [Hash{String=>Array<String>, String, nil}, nil] :query
255
- #
256
- # @option request_components [Hash{String=>String, nil}, nil] :headers
274
+ # Fail fast at request-build time on routes Bedrock does not support.
257
275
  #
258
- # @option request_components [Object, nil] :body
259
- #
260
- # @option request_components [Symbol, nil] :unwrap
261
- #
262
- # @option request_components [Class, nil] :page
263
- #
264
- # @option request_components [Anthropic::Converter, Class, nil] :model
265
- #
266
- # @return [Hash{Symbol=>Object}]
267
- #
268
- private def fit_req_to_bedrock_specs!(request_components)
269
- if (body = request_components[:body]).is_a?(Hash)
270
- body[:anthropic_version] ||= DEFAULT_VERSION
271
- body.transform_keys!("anthropic-beta": :anthropic_beta)
272
- end
273
-
274
- case request_components[:path]
276
+ # @param request_components [Hash{Symbol=>Object}]
277
+ # @return [void]
278
+ private def validate_bedrock_request!(request_components)
279
+ # Id-parameterized routes pass `path` as an Array whose first element
280
+ # is the format string (e.g. `["v1/messages/batches/%1$s", id]`).
281
+ case Array(request_components[:path]).first.to_s
275
282
  in %r{^v1/messages/batches}
276
283
  message = "The Batch API is not supported in Bedrock yet"
277
284
  raise NotImplementedError.new(message)
@@ -285,20 +292,45 @@ module Anthropic
285
292
  raise NotImplementedError.new(message)
286
293
  else
287
294
  end
295
+ end
288
296
 
289
- if %w[
290
- v1/complete
291
- v1/messages
292
- v1/messages?beta=true
293
- ].include?(request_components[:path]) && request_components[:method] == :post && body.is_a?(Hash)
294
- model = body.delete(:model)
295
- model = URI.encode_www_form_component(model.to_s)
296
- stream = body.delete(:stream) || false
297
- request_components[:path] =
298
- stream ? "model/#{model}/invoke-with-response-stream" : "model/#{model}/invoke"
299
- end
297
+ # @api private
298
+ #
299
+ # Rewrites the canonical Anthropic request into Bedrock's shape — drops
300
+ # `:model`/`:stream` from the body and retargets the URL to
301
+ # `/model/{model}/invoke[-with-response-stream]`. Called from
302
+ # {#provider_middleware}, so user middleware sees the canonical
303
+ # request. Pure: the incoming request, its body, and its URI are never
304
+ # mutated (they are reused across retry attempts).
305
+ #
306
+ # @param req [Anthropic::APIRequest]
307
+ # @return [Anthropic::APIRequest]
308
+ private def adapt_request(req)
309
+ body = req.body
310
+ return req unless body.is_a?(Hash)
311
+
312
+ body = body.transform_keys("anthropic-beta": :anthropic_beta)
313
+ body[:anthropic_version] ||= DEFAULT_VERSION
314
+
315
+ path = req.url.path.to_s
316
+ query = req.url.query
317
+ messages_route =
318
+ (path.end_with?("/v1/messages") && (query.nil? || query == "beta=true")) ||
319
+ (path.end_with?("/v1/complete") && query.nil?)
320
+
321
+ return req.with(body: body) unless req.method == :post && messages_route
322
+
323
+ model = URI.encode_www_form_component(body.delete(:model).to_s)
324
+ stream = body.delete(:stream) || false
325
+
326
+ url = req.url.dup
327
+ url.path = path.sub(
328
+ %r{v1/(?:messages|complete)\z},
329
+ stream ? "model/#{model}/invoke-with-response-stream" : "model/#{model}/invoke"
330
+ )
331
+ url.query = nil
300
332
 
301
- request_components
333
+ req.with(body: body, url: url)
302
334
  end
303
335
  end
304
336
  end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anthropic
4
+ module Helpers
5
+ module Bedrock
6
+ # @api private
7
+ #
8
+ # Bedrock's `invoke-with-response-stream` returns
9
+ # `application/vnd.amazon.eventstream` (AWS binary event-stream framing),
10
+ # not SSE. The SDK's stream consumer parses SSE only — without this
11
+ # transcoder a Bedrock stream silently yields zero events. Each frame's
12
+ # payload is `{"bytes": "<base64>"}` wrapping a standard Anthropic event
13
+ # JSON; this re-emits those as `event:`/`data:` SSE bytes so the existing
14
+ # `Internal::Util.decode_sse` and the streaming helpers work unchanged.
15
+ module EventStream
16
+ AWS_CONTENT_TYPE = %r{^application/vnd\.amazon\.eventstream}
17
+
18
+ class << self
19
+ # @api private
20
+ #
21
+ # Transcodes the raw AWS event-stream byte chunks into SSE-formatted
22
+ # byte chunks. Incremental: each input chunk is fed to
23
+ # `Aws::EventStream::Decoder` and any complete frames are emitted
24
+ # immediately, so the stream is not buffered end-to-end.
25
+ #
26
+ # @param chunks [Enumerable<String>] raw response body chunks
27
+ # @return [Enumerable<String>] SSE-formatted body chunks
28
+ def to_sse(chunks)
29
+ # `aws-eventstream` ships with `aws-sdk-core`, which the Bedrock
30
+ # client already lazy-requires before any request can fire.
31
+ require("aws-eventstream")
32
+ decoder = Aws::EventStream::Decoder.new
33
+ Anthropic::Internal::Util.chain_fused(chunks) do |y|
34
+ chunks.each { |chunk| drain(decoder, chunk, y) }
35
+ drain(decoder, nil, y)
36
+ end
37
+ end
38
+
39
+ # @api private
40
+ #
41
+ # Feeds one chunk (or `nil` to flush) into the decoder and emits any
42
+ # complete frames as SSE bytes.
43
+ #
44
+ # @param decoder [Aws::EventStream::Decoder]
45
+ # @param chunk [String, nil]
46
+ # @param y [Enumerator::Yielder]
47
+ # @return [void]
48
+ def drain(decoder, chunk, y)
49
+ loop do
50
+ msg, eof = decoder.decode_chunk(chunk)
51
+ chunk = nil
52
+ break if msg.nil?
53
+ emit(msg, y)
54
+ break if eof
55
+ end
56
+ end
57
+
58
+ # @api private
59
+ #
60
+ # Emit one decoded AWS event-stream message as SSE bytes.
61
+ #
62
+ # `:message-type: event` frames carry a JSON payload
63
+ # `{"bytes": "<base64>"}` wrapping the Anthropic event; emit it as
64
+ # an `event:`/`data:` pair. `:message-type: exception` frames carry an
65
+ # error payload and a `:exception-type` header — re-emit as the same
66
+ # `event: error` / `data: {"type":"error",...}` SSE shape the API
67
+ # would have sent, so the stream consumer's existing error path fires.
68
+ #
69
+ # @param msg [Aws::EventStream::Message]
70
+ # @param y [Enumerator::Yielder]
71
+ # @return [void]
72
+ def emit(msg, y)
73
+ case msg.headers[":message-type"]&.value
74
+ in "event"
75
+ payload = JSON.parse(msg.payload.read, symbolize_names: true)
76
+ inner = Base64.decode64(payload.fetch(:bytes))
77
+ type = JSON.parse(inner, symbolize_names: true).fetch(:type)
78
+ y << "event: #{type}\ndata: #{inner}\n\n"
79
+ in "exception"
80
+ exc_type = msg.headers[":exception-type"]&.value
81
+ body = msg.payload.read
82
+ data = JSON.generate(type: "error", error: {type: exc_type, message: body})
83
+ y << "event: error\ndata: #{data}\n\n"
84
+ else
85
+ # Unknown message-type — drop. AWS may add prelude/metadata frames.
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -114,9 +114,9 @@ module Anthropic
114
114
  end
115
115
 
116
116
  # @api private
117
- private def transform_request(request)
118
- aws_auth_transform_request(request)
119
- end
117
+ #
118
+ # @return [#call]
119
+ private def provider_middleware = method(:aws_auth_provider)
120
120
 
121
121
  # Restricted Beta service that only exposes messages. Other beta resources
122
122
  # (models, files, skills) are not supported on Bedrock Mantle.