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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +1 -1
- data/lib/anthropic/client.rb +8 -2
- data/lib/anthropic/errors.rb +14 -0
- data/lib/anthropic/helpers/aws/client.rb +3 -3
- data/lib/anthropic/helpers/aws_auth.rb +39 -29
- data/lib/anthropic/helpers/bedrock/client.rb +99 -67
- data/lib/anthropic/helpers/bedrock/event_stream.rb +92 -0
- data/lib/anthropic/helpers/bedrock/mantle_client.rb +3 -3
- data/lib/anthropic/helpers/vertex/client.rb +99 -55
- data/lib/anthropic/internal/transport/base_client.rb +196 -33
- data/lib/anthropic/internal/util.rb +37 -1
- data/lib/anthropic/middleware.rb +435 -0
- data/lib/anthropic/models/beta/beta_advisor_tool_20260301.rb +3 -1
- data/lib/anthropic/models/beta/beta_code_execution_tool_20250522.rb +3 -1
- data/lib/anthropic/models/beta/beta_code_execution_tool_20250825.rb +3 -1
- data/lib/anthropic/models/beta/beta_code_execution_tool_20260120.rb +3 -1
- data/lib/anthropic/models/beta/beta_code_execution_tool_20260521.rb +86 -0
- data/lib/anthropic/models/beta/beta_content_block.rb +3 -3
- data/lib/anthropic/models/beta/beta_content_block_param.rb +10 -12
- data/lib/anthropic/models/beta/beta_fallback_block.rb +13 -5
- data/lib/anthropic/models/beta/beta_fallback_block_param.rb +22 -13
- data/lib/anthropic/models/beta/beta_fallback_refusal_trigger.rb +44 -0
- data/lib/anthropic/models/beta/beta_memory_tool_20250818.rb +3 -1
- data/lib/anthropic/models/beta/beta_raw_content_block_start_event.rb +3 -3
- data/lib/anthropic/models/beta/beta_refusal_stop_details.rb +3 -7
- data/lib/anthropic/models/beta/beta_tool.rb +3 -1
- data/lib/anthropic/models/beta/beta_tool_bash_20241022.rb +3 -1
- data/lib/anthropic/models/beta/beta_tool_bash_20250124.rb +3 -1
- data/lib/anthropic/models/beta/beta_tool_computer_use_20241022.rb +3 -1
- data/lib/anthropic/models/beta/beta_tool_computer_use_20250124.rb +3 -1
- data/lib/anthropic/models/beta/beta_tool_computer_use_20251124.rb +3 -1
- data/lib/anthropic/models/beta/beta_tool_search_tool_bm25_20251119.rb +3 -1
- data/lib/anthropic/models/beta/beta_tool_search_tool_regex_20251119.rb +3 -1
- data/lib/anthropic/models/beta/beta_tool_text_editor_20241022.rb +3 -1
- data/lib/anthropic/models/beta/beta_tool_text_editor_20250124.rb +3 -1
- data/lib/anthropic/models/beta/beta_tool_text_editor_20250429.rb +3 -1
- data/lib/anthropic/models/beta/beta_tool_text_editor_20250728.rb +3 -1
- data/lib/anthropic/models/beta/beta_tool_union.rb +4 -1
- data/lib/anthropic/models/beta/beta_web_fetch_tool_20250910.rb +3 -1
- data/lib/anthropic/models/beta/beta_web_fetch_tool_20260209.rb +3 -1
- data/lib/anthropic/models/beta/beta_web_fetch_tool_20260309.rb +3 -1
- data/lib/anthropic/models/beta/beta_web_search_tool_20250305.rb +3 -1
- data/lib/anthropic/models/beta/beta_web_search_tool_20260209.rb +3 -1
- data/lib/anthropic/models/beta/beta_webhook_event.rb +2 -2
- data/lib/anthropic/models/beta/beta_webhook_event_data.rb +3 -1
- data/lib/anthropic/models/beta/beta_webhook_session_updated_event_data.rb +41 -0
- data/lib/anthropic/models/beta/message_count_tokens_params.rb +6 -3
- data/lib/anthropic/models/beta/message_create_params.rb +2 -2
- data/lib/anthropic/models/beta/messages/batch_create_params.rb +2 -2
- data/lib/anthropic/models/beta/unwrap_webhook_event.rb +2 -2
- data/lib/anthropic/models/code_execution_tool_20250522.rb +3 -1
- data/lib/anthropic/models/code_execution_tool_20250825.rb +3 -1
- data/lib/anthropic/models/code_execution_tool_20260120.rb +3 -1
- data/lib/anthropic/models/code_execution_tool_20260521.rb +82 -0
- data/lib/anthropic/models/memory_tool_20250818.rb +3 -1
- data/lib/anthropic/models/message_count_tokens_params.rb +2 -2
- data/lib/anthropic/models/message_count_tokens_tool.rb +4 -1
- data/lib/anthropic/models/message_create_params.rb +2 -2
- data/lib/anthropic/models/messages/batch_create_params.rb +2 -2
- data/lib/anthropic/models/refusal_stop_details.rb +3 -7
- data/lib/anthropic/models/tool.rb +3 -1
- data/lib/anthropic/models/tool_bash_20250124.rb +3 -1
- data/lib/anthropic/models/tool_search_tool_bm25_20251119.rb +3 -1
- data/lib/anthropic/models/tool_search_tool_regex_20251119.rb +3 -1
- data/lib/anthropic/models/tool_text_editor_20250124.rb +3 -1
- data/lib/anthropic/models/tool_text_editor_20250429.rb +3 -1
- data/lib/anthropic/models/tool_text_editor_20250728.rb +3 -1
- data/lib/anthropic/models/tool_union.rb +4 -1
- data/lib/anthropic/models/web_fetch_tool_20250910.rb +3 -1
- data/lib/anthropic/models/web_fetch_tool_20260209.rb +3 -1
- data/lib/anthropic/models/web_fetch_tool_20260309.rb +3 -1
- data/lib/anthropic/models/web_search_tool_20250305.rb +3 -1
- data/lib/anthropic/models/web_search_tool_20260209.rb +3 -1
- data/lib/anthropic/models.rb +2 -0
- data/lib/anthropic/request_options.rb +9 -0
- data/lib/anthropic/resources/beta/messages.rb +3 -3
- data/lib/anthropic/resources/messages.rb +3 -3
- data/lib/anthropic/version.rb +1 -1
- data/lib/anthropic.rb +6 -0
- data/rbi/anthropic/client.rbi +7 -2
- data/rbi/anthropic/errors.rbi +5 -0
- data/rbi/anthropic/helpers/aws/client.rbi +3 -6
- data/rbi/anthropic/helpers/bedrock/client.rbi +24 -13
- data/rbi/anthropic/helpers/bedrock/event_stream.rbi +25 -0
- data/rbi/anthropic/helpers/bedrock/mantle_client.rbi +3 -6
- data/rbi/anthropic/helpers/vertex/client.rbi +28 -8
- data/rbi/anthropic/internal/transport/base_client.rbi +39 -4
- data/rbi/anthropic/internal/util.rbi +5 -0
- data/rbi/anthropic/middleware.rbi +338 -0
- data/rbi/anthropic/models/beta/beta_advisor_tool_20260301.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_code_execution_tool_20250522.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_code_execution_tool_20250825.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_code_execution_tool_20260120.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_code_execution_tool_20260521.rbi +178 -0
- data/rbi/anthropic/models/beta/beta_fallback_block.rbi +19 -4
- data/rbi/anthropic/models/beta/beta_fallback_block_param.rbi +23 -13
- data/rbi/anthropic/models/beta/beta_fallback_refusal_trigger.rbi +108 -0
- data/rbi/anthropic/models/beta/beta_memory_tool_20250818.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_refusal_stop_details.rbi +3 -9
- data/rbi/anthropic/models/beta/beta_tool.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_tool_bash_20241022.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_tool_bash_20250124.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_tool_computer_use_20241022.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_tool_computer_use_20250124.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_tool_computer_use_20251124.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_tool_search_tool_bm25_20251119.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_tool_search_tool_regex_20251119.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_tool_text_editor_20241022.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_tool_text_editor_20250124.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_tool_text_editor_20250429.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_tool_text_editor_20250728.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_tool_union.rbi +1 -0
- data/rbi/anthropic/models/beta/beta_web_fetch_tool_20250910.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_web_fetch_tool_20260209.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_web_fetch_tool_20260309.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_web_search_tool_20250305.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_web_search_tool_20260209.rbi +7 -1
- data/rbi/anthropic/models/beta/beta_webhook_event.rbi +6 -3
- data/rbi/anthropic/models/beta/beta_webhook_event_data.rbi +2 -1
- data/rbi/anthropic/models/beta/beta_webhook_session_updated_event_data.rbi +63 -0
- data/rbi/anthropic/models/beta/message_count_tokens_params.rbi +5 -0
- data/rbi/anthropic/models/beta/message_create_params.rbi +4 -0
- data/rbi/anthropic/models/beta/messages/batch_create_params.rbi +4 -0
- data/rbi/anthropic/models/beta/unwrap_webhook_event.rbi +2 -1
- data/rbi/anthropic/models/code_execution_tool_20250522.rbi +7 -1
- data/rbi/anthropic/models/code_execution_tool_20250825.rbi +7 -1
- data/rbi/anthropic/models/code_execution_tool_20260120.rbi +7 -1
- data/rbi/anthropic/models/code_execution_tool_20260521.rbi +168 -0
- data/rbi/anthropic/models/memory_tool_20250818.rbi +7 -1
- data/rbi/anthropic/models/message_count_tokens_params.rbi +4 -0
- data/rbi/anthropic/models/message_count_tokens_tool.rbi +1 -0
- data/rbi/anthropic/models/message_create_params.rbi +4 -0
- data/rbi/anthropic/models/messages/batch_create_params.rbi +4 -0
- data/rbi/anthropic/models/refusal_stop_details.rbi +3 -9
- data/rbi/anthropic/models/tool.rbi +7 -1
- data/rbi/anthropic/models/tool_bash_20250124.rbi +7 -1
- data/rbi/anthropic/models/tool_search_tool_bm25_20251119.rbi +7 -1
- data/rbi/anthropic/models/tool_search_tool_regex_20251119.rbi +7 -1
- data/rbi/anthropic/models/tool_text_editor_20250124.rbi +7 -1
- data/rbi/anthropic/models/tool_text_editor_20250429.rbi +7 -1
- data/rbi/anthropic/models/tool_text_editor_20250728.rbi +7 -1
- data/rbi/anthropic/models/tool_union.rbi +1 -0
- data/rbi/anthropic/models/web_fetch_tool_20250910.rbi +7 -1
- data/rbi/anthropic/models/web_fetch_tool_20260209.rbi +7 -1
- data/rbi/anthropic/models/web_fetch_tool_20260309.rbi +7 -1
- data/rbi/anthropic/models/web_search_tool_20250305.rbi +7 -1
- data/rbi/anthropic/models/web_search_tool_20260209.rbi +7 -1
- data/rbi/anthropic/models.rbi +2 -0
- data/rbi/anthropic/request_options.rbi +5 -0
- data/rbi/anthropic/resources/beta/messages.rbi +3 -0
- data/rbi/anthropic/resources/messages.rbi +3 -0
- data/sig/anthropic/client.rbs +2 -1
- data/sig/anthropic/errors.rbs +3 -0
- data/sig/anthropic/helpers/bedrock/client.rbs +12 -4
- data/sig/anthropic/helpers/vertex/client.rbs +17 -4
- data/sig/anthropic/internal/transport/base_client.rbs +18 -3
- data/sig/anthropic/internal/util.rbs +2 -0
- data/sig/anthropic/middleware.rbs +117 -0
- data/sig/anthropic/models/beta/beta_advisor_tool_20260301.rbs +5 -1
- data/sig/anthropic/models/beta/beta_code_execution_tool_20250522.rbs +5 -1
- data/sig/anthropic/models/beta/beta_code_execution_tool_20250825.rbs +5 -1
- data/sig/anthropic/models/beta/beta_code_execution_tool_20260120.rbs +5 -1
- data/sig/anthropic/models/beta/beta_code_execution_tool_20260521.rbs +74 -0
- data/sig/anthropic/models/beta/beta_fallback_block.rbs +5 -0
- data/sig/anthropic/models/beta/beta_fallback_block_param.rbs +9 -2
- data/sig/anthropic/models/beta/beta_fallback_refusal_trigger.rbs +42 -0
- data/sig/anthropic/models/beta/beta_memory_tool_20250818.rbs +5 -1
- data/sig/anthropic/models/beta/beta_tool.rbs +5 -1
- data/sig/anthropic/models/beta/beta_tool_bash_20241022.rbs +5 -1
- data/sig/anthropic/models/beta/beta_tool_bash_20250124.rbs +5 -1
- data/sig/anthropic/models/beta/beta_tool_computer_use_20241022.rbs +5 -1
- data/sig/anthropic/models/beta/beta_tool_computer_use_20250124.rbs +5 -1
- data/sig/anthropic/models/beta/beta_tool_computer_use_20251124.rbs +5 -1
- data/sig/anthropic/models/beta/beta_tool_search_tool_bm25_20251119.rbs +5 -1
- data/sig/anthropic/models/beta/beta_tool_search_tool_regex_20251119.rbs +5 -1
- data/sig/anthropic/models/beta/beta_tool_text_editor_20241022.rbs +5 -1
- data/sig/anthropic/models/beta/beta_tool_text_editor_20250124.rbs +5 -1
- data/sig/anthropic/models/beta/beta_tool_text_editor_20250429.rbs +5 -1
- data/sig/anthropic/models/beta/beta_tool_text_editor_20250728.rbs +5 -1
- data/sig/anthropic/models/beta/beta_tool_union.rbs +1 -0
- data/sig/anthropic/models/beta/beta_web_fetch_tool_20250910.rbs +5 -1
- data/sig/anthropic/models/beta/beta_web_fetch_tool_20260209.rbs +5 -1
- data/sig/anthropic/models/beta/beta_web_fetch_tool_20260309.rbs +5 -1
- data/sig/anthropic/models/beta/beta_web_search_tool_20250305.rbs +5 -1
- data/sig/anthropic/models/beta/beta_web_search_tool_20260209.rbs +5 -1
- data/sig/anthropic/models/beta/beta_webhook_event_data.rbs +1 -0
- data/sig/anthropic/models/beta/beta_webhook_session_updated_event_data.rbs +39 -0
- data/sig/anthropic/models/beta/message_count_tokens_params.rbs +1 -0
- data/sig/anthropic/models/code_execution_tool_20250522.rbs +5 -1
- data/sig/anthropic/models/code_execution_tool_20250825.rbs +5 -1
- data/sig/anthropic/models/code_execution_tool_20260120.rbs +5 -1
- data/sig/anthropic/models/code_execution_tool_20260521.rbs +70 -0
- data/sig/anthropic/models/memory_tool_20250818.rbs +5 -1
- data/sig/anthropic/models/message_count_tokens_tool.rbs +1 -0
- data/sig/anthropic/models/tool.rbs +5 -1
- data/sig/anthropic/models/tool_bash_20250124.rbs +5 -1
- data/sig/anthropic/models/tool_search_tool_bm25_20251119.rbs +5 -1
- data/sig/anthropic/models/tool_search_tool_regex_20251119.rbs +5 -1
- data/sig/anthropic/models/tool_text_editor_20250124.rbs +5 -1
- data/sig/anthropic/models/tool_text_editor_20250429.rbs +5 -1
- data/sig/anthropic/models/tool_text_editor_20250728.rbs +5 -1
- data/sig/anthropic/models/tool_union.rbs +1 -0
- data/sig/anthropic/models/web_fetch_tool_20250910.rbs +5 -1
- data/sig/anthropic/models/web_fetch_tool_20260209.rbs +5 -1
- data/sig/anthropic/models/web_fetch_tool_20260309.rbs +5 -1
- data/sig/anthropic/models/web_search_tool_20250305.rbs +5 -1
- data/sig/anthropic/models/web_search_tool_20260209.rbs +5 -1
- data/sig/anthropic/models.rbs +2 -0
- data/sig/anthropic/request_options.rbs +4 -1
- metadata +19 -2
|
@@ -37,6 +37,11 @@ module Anthropic
|
|
|
37
37
|
#
|
|
38
38
|
# @param max_retry_delay [Float] The maximum number of seconds to wait before retrying a request
|
|
39
39
|
#
|
|
40
|
+
# @param middleware [Array<#call>, #call, nil] Per-attempt HTTP around-middleware. See
|
|
41
|
+
# {Anthropic::Middleware}. Middleware sees the canonical Anthropic request shape;
|
|
42
|
+
# the Vertex URL rewrite and OAuth header happen inside the continuation, per
|
|
43
|
+
# attempt.
|
|
44
|
+
#
|
|
40
45
|
def initialize(
|
|
41
46
|
region: ENV["CLOUD_ML_REGION"],
|
|
42
47
|
project_id: ENV["ANTHROPIC_VERTEX_PROJECT_ID"],
|
|
@@ -44,7 +49,8 @@ module Anthropic
|
|
|
44
49
|
max_retries: DEFAULT_MAX_RETRIES,
|
|
45
50
|
timeout: DEFAULT_TIMEOUT_IN_SECONDS,
|
|
46
51
|
initial_retry_delay: DEFAULT_INITIAL_RETRY_DELAY,
|
|
47
|
-
max_retry_delay: DEFAULT_MAX_RETRY_DELAY
|
|
52
|
+
max_retry_delay: DEFAULT_MAX_RETRY_DELAY,
|
|
53
|
+
middleware: nil
|
|
48
54
|
)
|
|
49
55
|
begin
|
|
50
56
|
require("googleauth")
|
|
@@ -81,6 +87,7 @@ module Anthropic
|
|
|
81
87
|
raise ArgumentError.new(message)
|
|
82
88
|
end
|
|
83
89
|
@project_id = project_id
|
|
90
|
+
@authorization = nil
|
|
84
91
|
|
|
85
92
|
base_url ||= ENV.fetch(
|
|
86
93
|
"ANTHROPIC_VERTEX_BASE_URL",
|
|
@@ -102,6 +109,7 @@ module Anthropic
|
|
|
102
109
|
max_retries: max_retries,
|
|
103
110
|
initial_retry_delay: initial_retry_delay,
|
|
104
111
|
max_retry_delay: max_retry_delay,
|
|
112
|
+
middleware: middleware
|
|
105
113
|
)
|
|
106
114
|
|
|
107
115
|
@messages = Anthropic::Resources::Messages.new(client: self)
|
|
@@ -146,58 +154,81 @@ module Anthropic
|
|
|
146
154
|
#
|
|
147
155
|
# @return [Hash{Symbol=>Object}]
|
|
148
156
|
private def build_request(req, opts)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
unless headers.key?("authorization")
|
|
156
|
-
authorization = Google::Auth.get_application_default(["https://www.googleapis.com/auth/cloud-platform"])
|
|
157
|
-
request_input.store(:headers, authorization.apply(headers))
|
|
157
|
+
# Id-parameterized routes pass `path` as an Array whose first element
|
|
158
|
+
# is the format string (e.g. `["v1/messages/batches/%1$s", id]`).
|
|
159
|
+
path = Array(req[:path]).first.to_s
|
|
160
|
+
if path.start_with?("v1/messages/batches")
|
|
161
|
+
raise NotImplementedError.new("The Batch API is not supported in the Vertex client yet")
|
|
158
162
|
end
|
|
159
163
|
|
|
160
|
-
|
|
164
|
+
super
|
|
161
165
|
end
|
|
162
166
|
|
|
163
|
-
# @private
|
|
164
|
-
#
|
|
165
|
-
# Overrides request components for Vertex-specific request-shape requirements.
|
|
166
|
-
#
|
|
167
|
-
# @param request_components [Hash{Symbol=>Object}] .
|
|
168
|
-
#
|
|
169
|
-
# @option request_components [Symbol] :method
|
|
170
|
-
#
|
|
171
|
-
# @option request_components [String, Array<String>] :path
|
|
172
|
-
#
|
|
173
|
-
# @option request_components [Hash{String=>Array<String>, String, nil}, nil] :query
|
|
174
|
-
#
|
|
175
|
-
# @option request_components [Hash{String=>String, nil}, nil] :headers
|
|
176
|
-
#
|
|
177
|
-
# @option request_components [Object, nil] :body
|
|
167
|
+
# @api private
|
|
178
168
|
#
|
|
179
|
-
#
|
|
169
|
+
# The Vertex provider middleware: rewrites the canonical request into
|
|
170
|
+
# Vertex's shape and applies the Google OAuth `authorization` header.
|
|
171
|
+
# Appended innermost on every dispatch (below user middleware) and runs
|
|
172
|
+
# per attempt, so a middleware that re-issues the request gets a fresh
|
|
173
|
+
# token per leg, mirroring the Bedrock SigV4 placement.
|
|
180
174
|
#
|
|
181
|
-
#
|
|
175
|
+
# @return [#call]
|
|
176
|
+
private def provider_middleware
|
|
177
|
+
lambda do |req, nxt|
|
|
178
|
+
nxt.call(apply_google_auth(adapt_request(req)))
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# @api private
|
|
182
183
|
#
|
|
183
|
-
#
|
|
184
|
+
# @param req [Anthropic::APIRequest]
|
|
185
|
+
# @return [Anthropic::APIRequest]
|
|
186
|
+
private def apply_google_auth(req)
|
|
187
|
+
return req if req.headers.key?("authorization")
|
|
188
|
+
# `follow_redirect` stripped `authorization` for a cross-origin hop —
|
|
189
|
+
# don't re-add it and leak the bearer token to the new origin.
|
|
190
|
+
return req if req.metadata[:cross_origin_redirect]
|
|
191
|
+
|
|
192
|
+
# Memoized: the credentials object caches its token and self-refreshes
|
|
193
|
+
# on expiry, so each retry leg still gets a fresh-enough token without
|
|
194
|
+
# re-resolving ADC (a blocking metadata-server/token-endpoint
|
|
195
|
+
# round-trip) on every attempt.
|
|
196
|
+
authorization =
|
|
197
|
+
@authorization ||= Google::Auth.get_application_default(["https://www.googleapis.com/auth/cloud-platform"])
|
|
198
|
+
# `req.headers` may be the deep-frozen hash a middleware saw, and
|
|
199
|
+
# googleauth's `#apply` does `clone` (which preserves frozen) then
|
|
200
|
+
# `[]=` — so it must be handed a fresh, mutable copy.
|
|
201
|
+
req.with(headers: authorization.apply({**req.headers}))
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# @api private
|
|
184
205
|
#
|
|
185
|
-
#
|
|
186
|
-
|
|
187
|
-
|
|
206
|
+
# Rewrites the canonical Anthropic request into Vertex's shape — drops
|
|
207
|
+
# `:model` from the body (keeping `:stream`) and retargets the URL to
|
|
208
|
+
# `projects/{project}/locations/{region}/publishers/anthropic/models/{model}:{rawPredict|streamRawPredict}`.
|
|
209
|
+
# Called from {#provider_middleware}, so user middleware sees the
|
|
210
|
+
# canonical request. Pure: the incoming request, its body, headers,
|
|
211
|
+
# and URI are never mutated (they are reused across retry attempts).
|
|
212
|
+
#
|
|
213
|
+
# @param req [Anthropic::APIRequest]
|
|
214
|
+
# @return [Anthropic::APIRequest]
|
|
215
|
+
private def adapt_request(req)
|
|
216
|
+
body = req.body
|
|
217
|
+
headers = req.headers
|
|
218
|
+
url = req.url
|
|
219
|
+
path = url.path.to_s
|
|
220
|
+
query_ok = url.query.nil? || url.query == "beta=true"
|
|
221
|
+
|
|
222
|
+
if body.is_a?(Hash)
|
|
223
|
+
body = body.dup
|
|
188
224
|
body[:anthropic_version] ||= DEFAULT_VERSION
|
|
189
225
|
|
|
190
226
|
if (anthropic_beta = body.delete(:"anthropic-beta"))
|
|
191
|
-
|
|
192
|
-
request_components[:headers]["anthropic-beta"] = anthropic_beta.join(",")
|
|
227
|
+
headers = headers.merge("anthropic-beta" => Array(anthropic_beta).join(","))
|
|
193
228
|
end
|
|
194
229
|
end
|
|
195
230
|
|
|
196
|
-
if
|
|
197
|
-
v1/messages
|
|
198
|
-
v1/messages?beta=true
|
|
199
|
-
].include?(request_components[:path]) && request_components[:method] == :post
|
|
200
|
-
|
|
231
|
+
if req.method == :post && query_ok && path.end_with?("/v1/messages")
|
|
201
232
|
unless body.is_a?(Hash)
|
|
202
233
|
raise ArgumentError.new("Expected json data to be a hash for post /v1/messages")
|
|
203
234
|
end
|
|
@@ -205,26 +236,39 @@ module Anthropic
|
|
|
205
236
|
model = body.delete(:model)
|
|
206
237
|
specifier = body[:stream] ? "streamRawPredict" : "rawPredict"
|
|
207
238
|
|
|
208
|
-
|
|
239
|
+
url = rewrite_path(
|
|
240
|
+
url,
|
|
241
|
+
%r{v1/messages\z},
|
|
209
242
|
"projects/#{@project_id}/locations/#{region}/publishers/anthropic/models/#{model}:#{specifier}"
|
|
210
|
-
|
|
243
|
+
)
|
|
244
|
+
elsif req.method == :post && query_ok && path.end_with?("/v1/messages/count_tokens")
|
|
245
|
+
url = rewrite_path(
|
|
246
|
+
url,
|
|
247
|
+
%r{v1/messages/count_tokens\z},
|
|
248
|
+
"projects/#{@project_id}/locations/#{region}/publishers/anthropic/" \
|
|
249
|
+
"models/count-tokens:rawPredict"
|
|
250
|
+
)
|
|
211
251
|
end
|
|
212
252
|
|
|
213
|
-
if
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
].include?(request_components[:path]) &&
|
|
217
|
-
request_components[:method] == :post
|
|
218
|
-
request_components[:path] =
|
|
219
|
-
"projects/#{@project_id}/locations/#{region}/publishers/anthropic/models/count-tokens:rawPredict"
|
|
220
|
-
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
if request_components[:path].start_with?("v1/messages/batches/")
|
|
224
|
-
raise AnthropicError("The Batch API is not supported in the Vertex client yet")
|
|
225
|
-
end
|
|
253
|
+
return req if body.equal?(req.body) && headers.equal?(req.headers) && url.equal?(req.url)
|
|
254
|
+
req.with(body: body, headers: headers, url: url)
|
|
255
|
+
end
|
|
226
256
|
|
|
227
|
-
|
|
257
|
+
# @api private
|
|
258
|
+
#
|
|
259
|
+
# Retarget `url`'s path (replacing `pattern` with `replacement`) and drop
|
|
260
|
+
# its query, returning a fresh copy so the incoming request's URI is left
|
|
261
|
+
# untouched.
|
|
262
|
+
#
|
|
263
|
+
# @param url [URI::Generic]
|
|
264
|
+
# @param pattern [Regexp]
|
|
265
|
+
# @param replacement [String]
|
|
266
|
+
# @return [URI::Generic]
|
|
267
|
+
private def rewrite_path(url, pattern, replacement)
|
|
268
|
+
url = url.dup
|
|
269
|
+
url.path = url.path.to_s.sub(pattern, replacement)
|
|
270
|
+
url.query = nil
|
|
271
|
+
url
|
|
228
272
|
end
|
|
229
273
|
end
|
|
230
274
|
end
|
|
@@ -132,7 +132,14 @@ module Anthropic
|
|
|
132
132
|
# from undici
|
|
133
133
|
if Anthropic::Internal::Util.uri_origin(url) != Anthropic::Internal::Util.uri_origin(location)
|
|
134
134
|
drop = %w[authorization cookie host proxy-authorization]
|
|
135
|
-
|
|
135
|
+
# The sentinel keeps the strip durable: provider middleware
|
|
136
|
+
# (Vertex OAuth, Bedrock SigV4) runs per attempt and would
|
|
137
|
+
# otherwise re-add `authorization` on the redirected leg.
|
|
138
|
+
request = {
|
|
139
|
+
**request,
|
|
140
|
+
headers: request.fetch(:headers).except(*drop),
|
|
141
|
+
metadata: {**request.fetch(:metadata, {}), cross_origin_redirect: true}
|
|
142
|
+
}
|
|
136
143
|
end
|
|
137
144
|
|
|
138
145
|
request
|
|
@@ -178,6 +185,9 @@ module Anthropic
|
|
|
178
185
|
# @return [Anthropic::Internal::Transport::PooledNetRequester]
|
|
179
186
|
attr_reader :requester
|
|
180
187
|
|
|
188
|
+
# @return [Array<#call>] the middleware chain (first = outermost).
|
|
189
|
+
attr_reader :middleware
|
|
190
|
+
|
|
181
191
|
# @api private
|
|
182
192
|
#
|
|
183
193
|
# @param base_url [String]
|
|
@@ -187,6 +197,8 @@ module Anthropic
|
|
|
187
197
|
# @param max_retry_delay [Float]
|
|
188
198
|
# @param headers [Hash{String=>String, Integer, Array<String, Integer, nil>, nil}]
|
|
189
199
|
# @param idempotency_header [String, nil]
|
|
200
|
+
# @param middleware [Array<#call>, #call, nil]
|
|
201
|
+
# @param requester [Anthropic::Internal::Transport::PooledNetRequester, nil]
|
|
190
202
|
def initialize(
|
|
191
203
|
base_url:,
|
|
192
204
|
timeout: 0.0,
|
|
@@ -194,9 +206,12 @@ module Anthropic
|
|
|
194
206
|
initial_retry_delay: 0.0,
|
|
195
207
|
max_retry_delay: 0.0,
|
|
196
208
|
headers: {},
|
|
197
|
-
idempotency_header: nil
|
|
209
|
+
idempotency_header: nil,
|
|
210
|
+
middleware: nil,
|
|
211
|
+
requester: nil
|
|
198
212
|
)
|
|
199
|
-
@
|
|
213
|
+
@middleware = Array(middleware).freeze
|
|
214
|
+
@requester = requester || Anthropic::Internal::Transport::PooledNetRequester.new
|
|
200
215
|
@headers = Anthropic::Internal::Util.normalized_headers(
|
|
201
216
|
self.class::PLATFORM_HEADERS,
|
|
202
217
|
{
|
|
@@ -316,15 +331,30 @@ module Anthropic
|
|
|
316
331
|
@base_url_components,
|
|
317
332
|
{**req, path: path, query: query}
|
|
318
333
|
)
|
|
319
|
-
|
|
334
|
+
|
|
335
|
+
# Per-request middleware rides in `request_options` but is kept out of
|
|
336
|
+
# the `options` view a middleware sees — it is the live chain, not
|
|
337
|
+
# serializable request configuration, and surfacing it there would be
|
|
338
|
+
# self-referential.
|
|
339
|
+
request_middleware = Array(opts[:middleware])
|
|
340
|
+
|
|
320
341
|
{
|
|
321
342
|
method: method,
|
|
322
343
|
url: url,
|
|
323
344
|
headers: headers,
|
|
324
|
-
body:
|
|
345
|
+
body: body,
|
|
325
346
|
max_retries: opts.fetch(:max_retries, @max_retries),
|
|
326
347
|
timeout: timeout,
|
|
327
|
-
user_header_keys: user_header_keys
|
|
348
|
+
user_header_keys: user_header_keys,
|
|
349
|
+
# For paginated requests `model` is the page's *item* type, not the
|
|
350
|
+
# response envelope's — middleware `parse` falls back to the raw
|
|
351
|
+
# decoded page data instead of mis-coercing.
|
|
352
|
+
cast_to: req[:page] ? nil : req.fetch(:model, Anthropic::Internal::Type::Unknown),
|
|
353
|
+
stream: req[:stream],
|
|
354
|
+
unwrap: req[:unwrap],
|
|
355
|
+
options: opts.except(:middleware),
|
|
356
|
+
middleware: request_middleware,
|
|
357
|
+
metadata: {}
|
|
328
358
|
}
|
|
329
359
|
end
|
|
330
360
|
|
|
@@ -356,20 +386,24 @@ module Anthropic
|
|
|
356
386
|
|
|
357
387
|
# @api private
|
|
358
388
|
#
|
|
359
|
-
#
|
|
389
|
+
# The default innermost middleware. 3p provider clients
|
|
390
|
+
# (Bedrock/Vertex/AWS) override this to return a `#call(req, nxt)`
|
|
391
|
+
# entry that rewrites the canonical Anthropic request into the
|
|
392
|
+
# provider's wire shape and applies provider auth (SigV4 signing,
|
|
393
|
+
# OAuth tokens).
|
|
360
394
|
#
|
|
361
|
-
#
|
|
395
|
+
# It is appended below all user middleware on every dispatch — client-
|
|
396
|
+
# and request-level entries alike always see the canonical request
|
|
397
|
+
# (`body[:model]`, `/v1/messages` URL) regardless of provider
|
|
398
|
+
# ("3p-inner" ordering) — and, like any middleware, runs per attempt, so
|
|
399
|
+
# SDK retries and middleware-re-issued requests are re-adapted and
|
|
400
|
+
# re-signed for their own leg.
|
|
362
401
|
#
|
|
363
|
-
#
|
|
402
|
+
# The requests it receives are reused across retry attempts — it MUST
|
|
403
|
+
# be pure and must not mutate `req` or any object reachable from it.
|
|
364
404
|
#
|
|
365
|
-
#
|
|
366
|
-
|
|
367
|
-
# @option request [Hash{String=>String}] :headers
|
|
368
|
-
#
|
|
369
|
-
# @option request [Object] :body
|
|
370
|
-
#
|
|
371
|
-
# @return [Hash{Symbol, Object}]
|
|
372
|
-
private def transform_request(request) = request
|
|
405
|
+
# @return [#call, nil]
|
|
406
|
+
private def provider_middleware = nil
|
|
373
407
|
|
|
374
408
|
# @api private
|
|
375
409
|
#
|
|
@@ -381,6 +415,28 @@ module Anthropic
|
|
|
381
415
|
self.class.should_retry?(status, headers: headers)
|
|
382
416
|
end
|
|
383
417
|
|
|
418
|
+
# @api private
|
|
419
|
+
#
|
|
420
|
+
# Whether `err` (or any error reachable via `Exception#cause`) is one
|
|
421
|
+
# the SDK retries — {Anthropic::Errors::RetryableError},
|
|
422
|
+
# {Anthropic::Errors::APIConnectionError}, or its subclass
|
|
423
|
+
# {Anthropic::Errors::APITimeoutError}.
|
|
424
|
+
#
|
|
425
|
+
# @param err [Exception]
|
|
426
|
+
# @return [Boolean]
|
|
427
|
+
private def retryable_error?(err)
|
|
428
|
+
seen = {}.compare_by_identity
|
|
429
|
+
while err && !seen[err]
|
|
430
|
+
seen[err] = true
|
|
431
|
+
case err
|
|
432
|
+
when Anthropic::Errors::RetryableError, Anthropic::Errors::APIConnectionError
|
|
433
|
+
return true
|
|
434
|
+
end
|
|
435
|
+
err = err.cause
|
|
436
|
+
end
|
|
437
|
+
false
|
|
438
|
+
end
|
|
439
|
+
|
|
384
440
|
# @api private
|
|
385
441
|
#
|
|
386
442
|
# @param request [Hash{Symbol=>Object}] .
|
|
@@ -404,42 +460,137 @@ module Anthropic
|
|
|
404
460
|
# @param send_retry_header [Boolean]
|
|
405
461
|
#
|
|
406
462
|
# @raise [Anthropic::Errors::APIError]
|
|
407
|
-
# @return [Array(Integer, Net::HTTPResponse, Enumerable<String>)]
|
|
463
|
+
# @return [Array(Integer, Net::HTTPResponse|nil, Hash{String=>String}, Enumerable<String>)]
|
|
408
464
|
def send_request(request, redirect_count:, retry_count:, send_retry_header:)
|
|
409
465
|
if send_retry_header
|
|
410
466
|
request.fetch(:headers)["x-stainless-retry-count"] = retry_count.to_s
|
|
411
467
|
end
|
|
412
468
|
|
|
413
|
-
request = transform_request(request)
|
|
414
469
|
url, max_retries, timeout = request.fetch_values(:url, :max_retries, :timeout)
|
|
415
|
-
|
|
470
|
+
|
|
471
|
+
# Request-level middleware (from `request_options`) runs innermost
|
|
472
|
+
# among user middleware — below client-level entries, above the
|
|
473
|
+
# provider middleware.
|
|
474
|
+
request_middleware = request.fetch(:middleware, [])
|
|
475
|
+
|
|
476
|
+
# Per-attempt defensive copies: the `request` hash and its members are
|
|
477
|
+
# reused across retry recursions (the SDK mutates `request[:headers]`
|
|
478
|
+
# for retry-count/auth stamping), so the immutable view middleware sees
|
|
479
|
+
# must not alias them. With no user middleware at all only the terminal
|
|
480
|
+
# and the provider middleware read these (both are pure), so the deep
|
|
481
|
+
# freeze is skipped. Only `headers` change across retries, so the frozen
|
|
482
|
+
# `url`/`body`/`options` copies are cached on `request` and reused; the
|
|
483
|
+
# redirect path drops the stale `url`/`body` copies. `metadata` is the
|
|
484
|
+
# deliberate exception — it is the cross-attempt scratchpad a middleware
|
|
485
|
+
# may write to.
|
|
486
|
+
user_middleware_empty = @middleware.empty? && request_middleware.empty?
|
|
487
|
+
freeze = user_middleware_empty ? ->(o) { o } : Anthropic::Internal::Util.method(:deep_frozen_copy)
|
|
488
|
+
api_req = Anthropic::APIRequest.new(
|
|
489
|
+
method: request.fetch(:method),
|
|
490
|
+
url: (request[:frozen_url] ||= freeze.call(url)),
|
|
491
|
+
headers: freeze.call(request.fetch(:headers)),
|
|
492
|
+
body: (request[:frozen_body] ||= freeze.call(request[:body])),
|
|
493
|
+
stream: request[:stream],
|
|
494
|
+
cast_to: request[:cast_to],
|
|
495
|
+
unwrap: request[:unwrap],
|
|
496
|
+
options: (
|
|
497
|
+
request[:frozen_options] ||= freeze.call(request.fetch(:options, {}).merge(timeout: timeout))
|
|
498
|
+
),
|
|
499
|
+
retry_count: retry_count,
|
|
500
|
+
metadata: request.fetch(:metadata, {})
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
# The provider request the response actually came from — captured
|
|
504
|
+
# inside the terminal so error reporting, relative-redirect
|
|
505
|
+
# resolution, and redirect re-sends see the post-`provider_middleware`
|
|
506
|
+
# (Bedrock/Vertex) URL and body, not the canonical `/v1/messages`
|
|
507
|
+
# shape. Falls back to the canonical values when middleware
|
|
508
|
+
# short-circuits before `nxt.call`.
|
|
509
|
+
adapted_url = url
|
|
510
|
+
adapted_body = request[:body]
|
|
511
|
+
attempt_body = nil
|
|
512
|
+
attempt_headers = nil
|
|
513
|
+
terminal = lambda do |r|
|
|
514
|
+
adapted_url = r.url
|
|
515
|
+
adapted_body = r.body
|
|
516
|
+
enc_headers, encoded = Anthropic::Internal::Util.encode_content(r.headers, r.body)
|
|
517
|
+
input = {
|
|
518
|
+
method: r.method,
|
|
519
|
+
url: r.url,
|
|
520
|
+
headers: enc_headers,
|
|
521
|
+
body: encoded,
|
|
522
|
+
deadline: Anthropic::Internal::Util.monotonic_secs + timeout
|
|
523
|
+
}
|
|
524
|
+
http_status, raw, body = @requester.execute(input)
|
|
525
|
+
attempt_body = body
|
|
526
|
+
res = Anthropic::APIResponse.wrap(http_status, raw, body, request: r)
|
|
527
|
+
attempt_headers = res.headers
|
|
528
|
+
res
|
|
529
|
+
end
|
|
416
530
|
|
|
417
531
|
begin
|
|
418
|
-
|
|
419
|
-
|
|
532
|
+
chain = Anthropic::Middleware.build_chain(
|
|
533
|
+
[*@middleware, *request_middleware, *provider_middleware],
|
|
534
|
+
terminal
|
|
535
|
+
)
|
|
536
|
+
mres = chain.call(api_req)
|
|
537
|
+
unless mres.is_a?(Anthropic::APIResponse)
|
|
538
|
+
raise TypeError,
|
|
539
|
+
"middleware returned #{mres.class}, expected Anthropic::APIResponse"
|
|
540
|
+
end
|
|
541
|
+
status, response, headers, stream = mres.to_tuple
|
|
542
|
+
rescue StandardError => e
|
|
543
|
+
# A middleware may raise after `nxt.call` returned a live response
|
|
544
|
+
# (e.g. `raise RetryableError if res.status >= 500`); release the
|
|
545
|
+
# pooled connection its un-consumed body still holds.
|
|
546
|
+
Anthropic::Internal::Util.close_fused!(attempt_body)
|
|
547
|
+
raise unless retryable_error?(e)
|
|
420
548
|
status = e
|
|
549
|
+
# A middleware may raise after observing a live response (e.g. a 429
|
|
550
|
+
# carrying `Retry-After`); keep that response's headers so retry
|
|
551
|
+
# backoff still honors server-driven delay. `nil` when the terminal
|
|
552
|
+
# itself raised — a connection failure carries no response.
|
|
553
|
+
headers = attempt_headers || {}
|
|
421
554
|
end
|
|
422
|
-
headers = Anthropic::Internal::Util.normalized_headers(response&.each_header&.to_h)
|
|
423
555
|
|
|
424
556
|
case status
|
|
425
557
|
in ..299
|
|
426
|
-
[status, response, stream]
|
|
558
|
+
[status, response, headers, stream]
|
|
427
559
|
in 300..399 if redirect_count >= self.class::MAX_REDIRECTS
|
|
428
560
|
self.class.reap_connection!(status, stream: stream)
|
|
429
561
|
|
|
430
562
|
message = "Failed to complete the request within #{self.class::MAX_REDIRECTS} redirects."
|
|
431
|
-
raise Anthropic::Errors::APIConnectionError.new(
|
|
563
|
+
raise Anthropic::Errors::APIConnectionError.new(
|
|
564
|
+
url: adapted_url,
|
|
565
|
+
response: response,
|
|
566
|
+
message: message
|
|
567
|
+
)
|
|
432
568
|
in 300..399
|
|
433
569
|
self.class.reap_connection!(status, stream: stream)
|
|
434
570
|
|
|
435
|
-
|
|
571
|
+
# Re-send the provider-adapted body: the redirect target is a
|
|
572
|
+
# provider URL that fails the provider middleware's adapt gate, so
|
|
573
|
+
# the canonical body would otherwise go out un-adapted on
|
|
574
|
+
# Bedrock/Vertex. A SigV4-signed `StringIO` body was consumed by
|
|
575
|
+
# the prior attempt and must be rewound before re-signing.
|
|
576
|
+
adapted_body.rewind if adapted_body.is_a?(StringIO) || adapted_body.is_a?(IO)
|
|
577
|
+
request = self.class.follow_redirect(
|
|
578
|
+
request.merge(url: adapted_url, body: adapted_body),
|
|
579
|
+
status: status,
|
|
580
|
+
response_headers: headers
|
|
581
|
+
)
|
|
582
|
+
# The redirect retargets the URL, and `follow_redirect` may drop the
|
|
583
|
+
# body (303 → GET); the cached frozen copies would then be stale, so
|
|
584
|
+
# they are recomputed on the redirected attempt.
|
|
585
|
+
request.delete(:frozen_body)
|
|
586
|
+
request.delete(:frozen_url)
|
|
436
587
|
send_request(
|
|
437
588
|
request,
|
|
438
589
|
redirect_count: redirect_count + 1,
|
|
439
590
|
retry_count: retry_count,
|
|
440
591
|
send_retry_header: send_retry_header
|
|
441
592
|
)
|
|
442
|
-
in
|
|
593
|
+
in Exception if retry_count >= max_retries
|
|
443
594
|
raise status
|
|
444
595
|
in (400..) if retry_count >= max_retries || !retry_request?(status, headers: headers)
|
|
445
596
|
decoded = Kernel.then do
|
|
@@ -449,17 +600,17 @@ module Anthropic
|
|
|
449
600
|
end
|
|
450
601
|
|
|
451
602
|
raise Anthropic::Errors::APIStatusError.for(
|
|
452
|
-
url:
|
|
603
|
+
url: adapted_url,
|
|
453
604
|
status: status,
|
|
454
605
|
headers: headers,
|
|
455
606
|
body: decoded,
|
|
456
607
|
request: nil,
|
|
457
608
|
response: response
|
|
458
609
|
)
|
|
459
|
-
in (400..) |
|
|
610
|
+
in (400..) | Exception
|
|
460
611
|
self.class.reap_connection!(status, stream: stream)
|
|
461
612
|
|
|
462
|
-
delay = retry_delay(
|
|
613
|
+
delay = retry_delay(headers, retry_count: retry_count)
|
|
463
614
|
sleep(delay)
|
|
464
615
|
|
|
465
616
|
# Refresh auth headers across retries: credential providers (e.g. OAuth
|
|
@@ -533,14 +684,13 @@ module Anthropic
|
|
|
533
684
|
|
|
534
685
|
# Don't send the current retry count in the headers if the caller modified the header defaults.
|
|
535
686
|
send_retry_header = request.fetch(:headers)["x-stainless-retry-count"] == "0"
|
|
536
|
-
status, response, stream = send_request(
|
|
687
|
+
status, response, headers, stream = send_request(
|
|
537
688
|
request,
|
|
538
689
|
redirect_count: 0,
|
|
539
690
|
retry_count: 0,
|
|
540
691
|
send_retry_header: send_retry_header
|
|
541
692
|
)
|
|
542
693
|
|
|
543
|
-
headers = Anthropic::Internal::Util.normalized_headers(response.each_header.to_h)
|
|
544
694
|
decoded = Anthropic::Internal::Util.decode_content(headers, stream: stream)
|
|
545
695
|
case req
|
|
546
696
|
in {stream: Class => st}
|
|
@@ -613,7 +763,20 @@ module Anthropic
|
|
|
613
763
|
body: T.anything,
|
|
614
764
|
max_retries: Integer,
|
|
615
765
|
timeout: Float,
|
|
616
|
-
user_header_keys: T::Array[String]
|
|
766
|
+
user_header_keys: T::Array[String],
|
|
767
|
+
cast_to: T.nilable(Anthropic::Internal::Type::Converter::Input),
|
|
768
|
+
stream: T.nilable(T::Class[T.anything]),
|
|
769
|
+
unwrap: T.nilable(
|
|
770
|
+
T.any(
|
|
771
|
+
Symbol,
|
|
772
|
+
Integer,
|
|
773
|
+
T::Array[T.any(Symbol, Integer)],
|
|
774
|
+
T.proc.params(arg0: T.anything).returns(T.anything)
|
|
775
|
+
)
|
|
776
|
+
),
|
|
777
|
+
options: T::Hash[Symbol, T.anything],
|
|
778
|
+
middleware: T::Array[T.anything],
|
|
779
|
+
metadata: T::Hash[Symbol, T.anything]
|
|
617
780
|
}
|
|
618
781
|
end
|
|
619
782
|
end
|
|
@@ -27,6 +27,38 @@ module Anthropic
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
class << self
|
|
30
|
+
# @api private
|
|
31
|
+
#
|
|
32
|
+
# Structurally copy and freeze `obj` (Hash/Array recursively; mutable
|
|
33
|
+
# `String` leaves dup-frozen; `URI::Generic` re-parsed with each
|
|
34
|
+
# component string frozen) so the result is fully de-aliased from the
|
|
35
|
+
# input and any in-place mutation raises `FrozenError`. Other leaves
|
|
36
|
+
# (Integer/Symbol — already frozen; typed models; IO) pass through, IO
|
|
37
|
+
# because it must stay live for streamed uploads.
|
|
38
|
+
#
|
|
39
|
+
# @param obj [Object]
|
|
40
|
+
# @return [Object]
|
|
41
|
+
def deep_frozen_copy(obj)
|
|
42
|
+
case obj
|
|
43
|
+
when Hash
|
|
44
|
+
obj.transform_values { deep_frozen_copy(_1) }.freeze
|
|
45
|
+
when Array
|
|
46
|
+
obj.map { deep_frozen_copy(_1) }.freeze
|
|
47
|
+
when String
|
|
48
|
+
obj.frozen? ? obj : obj.dup.freeze
|
|
49
|
+
when URI::Generic
|
|
50
|
+
# Re-parse de-aliases every component string; freezing each via its
|
|
51
|
+
# public reader (then the URI) makes `url.path << "x"` raise without
|
|
52
|
+
# reaching below `URI::Generic`'s public surface. Callers `dup` then
|
|
53
|
+
# reassign components, so a frozen URI is fine to derive from.
|
|
54
|
+
URI.parse(obj.to_s).tap do |u|
|
|
55
|
+
[u.scheme, u.userinfo, u.host, u.path, u.query, u.opaque, u.fragment].each { _1&.freeze }
|
|
56
|
+
end.freeze
|
|
57
|
+
else
|
|
58
|
+
obj
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
30
62
|
# @api private
|
|
31
63
|
#
|
|
32
64
|
# @return [String]
|
|
@@ -658,7 +690,11 @@ module Anthropic
|
|
|
658
690
|
[headers, JSON.generate(body)]
|
|
659
691
|
in [Anthropic::Internal::Util::JSONL_CONTENT, Enumerable] unless Anthropic::Internal::Type::FileInput === body
|
|
660
692
|
[headers, body.lazy.map { JSON.generate(_1) }]
|
|
661
|
-
in
|
|
693
|
+
# A `boundary=` already in the content-type means the body was
|
|
694
|
+
# encoded upstream (e.g. SigV4 signing encodes-then-signs and the
|
|
695
|
+
# terminal calls this again) — fall through to the pass-through arms
|
|
696
|
+
# instead of re-wrapping the signed bytes with a second boundary.
|
|
697
|
+
in [%r{^multipart/form-data}, Hash | Anthropic::Internal::Type::FileInput] unless content_type.include?("boundary=")
|
|
662
698
|
boundary, strio = encode_multipart_streaming(body)
|
|
663
699
|
headers = {**headers, "content-type" => "#{content_type}; boundary=#{boundary}"}
|
|
664
700
|
[headers, strio]
|