class Anthropic::Internal::Transport::BaseClient

@abstract
@api private

def auth_headers = {}

Returns:
  • (Hash{String=>String}) -

Other tags:
    Api: - private
def auth_headers = {}

def build_request(req, opts)

Returns:
  • (Hash{Symbol=>Object}) -

Options Hash: (**opts)
  • :timeout (Float, nil) --
  • :max_retries (Integer, nil) --
  • :extra_body (Object, nil) --
  • :extra_headers (Hash{String=>String, nil}, nil) --
  • :extra_query (Hash{String=>Array, String, nil}, nil) --
  • :idempotency_key (String, nil) --
  • :model (Anthropic::Internal::Type::Converter, Class, nil) --
  • :stream (Class, nil) --
  • :page (Class, nil) --
  • :unwrap (Symbol, nil) --
  • :body (Object, nil) --
  • :headers (Hash{String=>String, Integer, Array, nil}, nil) --
  • :query (Hash{String=>Array, String, nil}, nil) --
  • :path (String, Array) --
  • :method (Symbol) --

Parameters:
  • opts (Hash{Symbol=>Object}) -- .
  • req (Hash{Symbol=>Object}) -- .

Other tags:
    Api: - private
def build_request(req, opts)
, uninterpolated_path = req.fetch_values(:method, :path)
 Anthropic::Internal::Util.interpolate_path(uninterpolated_path)
= Anthropic::Internal::Util.deep_merge(req[:query].to_h, opts[:extra_query].to_h)
s = Anthropic::Internal::Util.normalized_headers(
ders,
_headers,
:headers].to_h,
[:extra_headers].to_h
empotency_header &&
aders.key?(@idempotency_header) &&
t::HTTP::IDEMPOTENT_METHODS_.include?(method.to_s.upcase)
ers[@idempotency_header] = opts.fetch(:idempotency_key) { generate_idempotency_key }
 headers.key?("x-stainless-retry-count")
ers["x-stainless-retry-count"] = "0"
t = opts.fetch(:timeout, @timeout).to_f.clamp((0..))
 headers.key?("x-stainless-timeout") || timeout.zero?
ers["x-stainless-timeout"] = timeout.to_s
s.reject! { |_, v| v.to_s.empty? }

 method
get | :head | :options | :trace
l

thropic::Internal::Util.deep_merge(*[req[:body], opts[:extra_body]].compact)
s, encoded = Anthropic::Internal::Util.encode_content(headers, body)
od: method,
 Anthropic::Internal::Util.join_parsed_uri(@base_url, {**req, path: path, query: query}),
ers: headers,
: encoded,
retries: opts.fetch(:max_retries, @max_retries),
out: timeout

def follow_redirect(request, status:, response_headers:)

Returns:
  • (Hash{Symbol=>Object}) -

Parameters:
  • response_headers (Hash{String=>String}, Net::HTTPHeader) --
  • status (Integer) --
  • request (Hash{Symbol=>Object}) -- .

Options Hash: (**request)
  • :timeout (Float) --
  • :max_retries (Integer) --
  • :body (Object) --
  • :headers (Hash{String=>String}) --
  • :url (URI::Generic) --
  • :method (Symbol) --

Other tags:
    Api: - private
def follow_redirect(request, status:, response_headers:)
  method, url, headers = request.fetch_values(:method, :url, :headers)
  location =
    Kernel.then do
      URI.join(url, response_headers["location"])
    rescue ArgumentError
      message = "Server responded with status #{status} but no valid location header."
      raise Anthropic::Errors::APIConnectionError.new(url: url, message: message)
    end
  request = {**request, url: location}
  case [url.scheme, location.scheme]
  in ["https", "http"]
    message = "Tried to redirect to a insecure URL"
    raise Anthropic::Errors::APIConnectionError.new(url: url, message: message)
  else
    nil
  end
  # from whatwg fetch spec
  case [status, method]
  in [301 | 302, :post] | [303, _]
    drop = %w[content-encoding content-language content-length content-location content-type]
    request = {
      **request,
      method: method == :head ? :head : :get,
      headers: headers.except(*drop),
      body: nil
    }
  else
  end
  # from undici
  if Anthropic::Internal::Util.uri_origin(url) != Anthropic::Internal::Util.uri_origin(location)
    drop = %w[authorization cookie host proxy-authorization]
    request = {**request, headers: request.fetch(:headers).except(*drop)}
  end
  request
end

def generate_idempotency_key = "stainless-ruby-retry-#{SecureRandom.uuid}"

Returns:
  • (String) -

Other tags:
    Api: - private
def generate_idempotency_key = "stainless-ruby-retry-#{SecureRandom.uuid}"

def initialize(

Parameters:
  • idempotency_header (String, nil) --
  • headers (Hash{String=>String, Integer, Array, nil}) --
  • max_retry_delay (Float) --
  • initial_retry_delay (Float) --
  • max_retries (Integer) --
  • timeout (Float) --
  • base_url (String) --

Other tags:
    Api: - private
def initialize(
  base_url:,
  timeout: 0.0,
  max_retries: 0,
  initial_retry_delay: 0.0,
  max_retry_delay: 0.0,
  headers: {},
  idempotency_header: nil
)
  @requester = Anthropic::Internal::Transport::PooledNetRequester.new
  @headers = Anthropic::Internal::Util.normalized_headers(
    self.class::PLATFORM_HEADERS,
    {
      "accept" => "application/json",
      "content-type" => "application/json"
    },
    headers
  )
  @base_url = Anthropic::Internal::Util.parse_uri(base_url)
  @idempotency_header = idempotency_header&.to_s&.downcase
  @max_retries = max_retries
  @timeout = timeout
  @initial_retry_delay = initial_retry_delay
  @max_retry_delay = max_retry_delay
end

def inspect

Returns:
  • (String) -
def inspect
  # rubocop:disable Layout/LineLength
  base_url = Anthropic::Internal::Util.unparse_uri(@base_url)
  "#<#{self.class.name}:0x#{object_id.to_s(16)} base_url=#{base_url} max_retries=#{@max_retries} timeout=#{@timeout}>"
  # rubocop:enable Layout/LineLength
end

def reap_connection!(status, stream:)

Parameters:
  • stream (Enumerable, nil) --
  • status (Integer, Anthropic::Errors::APIConnectionError) --

Other tags:
    Api: - private
def reap_connection!(status, stream:)
  case status
  in (..199) | (300..499)
    stream&.each { next }
  in Anthropic::Errors::APIConnectionError | (500..)
    Anthropic::Internal::Util.close_fused!(stream)
  else
  end
end

def request(req)

Returns:
  • (Object) -

Raises:
  • (Anthropic::Errors::APIError) -

Options Hash: (**options)
  • :timeout (Float, nil) --
  • :max_retries (Integer, nil) --
  • :extra_body (Object, nil) --
  • :extra_headers (Hash{String=>String, nil}, nil) --
  • :extra_query (Hash{String=>Array, String, nil}, nil) --
  • :idempotency_key (String, nil) --

Parameters:
  • options (Anthropic::RequestOptions, Hash{Symbol=>Object}, nil) -- .
  • model (Anthropic::Internal::Type::Converter, Class, nil) --
  • stream (Class, nil) --
  • page (Class, nil) --
  • unwrap (Symbol, nil) --
  • body (Object, nil) --
  • headers (Hash{String=>String, Integer, Array, nil}, nil) --
  • query (Hash{String=>Array, String, nil}, nil) --
  • path (String, Array) --
  • method (Symbol) --

Overloads:
  • request(method, path, query: {}, headers: {}, body: nil, unwrap: nil, page: nil, stream: nil, model: Anthropic::Internal::Type::Unknown, options: {})
def request(req)
  self.class.validate!(req)
  model = req.fetch(:model) { Anthropic::Internal::Type::Unknown }
  opts = req[:options].to_h
  Anthropic::RequestOptions.validate!(opts)
  request = build_request(req.except(:options), opts)
  url = request.fetch(:url)
  # Don't send the current retry count in the headers if the caller modified the header defaults.
  send_retry_header = request.fetch(:headers)["x-stainless-retry-count"] == "0"
  status, response, stream = send_request(
    request,
    redirect_count: 0,
    retry_count: 0,
    send_retry_header: send_retry_header
  )
  decoded = Anthropic::Internal::Util.decode_content(response, stream: stream)
  case req
  in { stream: Class => st }
    st.new(model: model, url: url, status: status, response: response, stream: decoded)
  in { page: Class => page }
    page.new(client: self, req: req, headers: response, page_data: decoded)
  else
    unwrapped = Anthropic::Internal::Util.dig(decoded, req[:unwrap])
    Anthropic::Internal::Type::Converter.coerce(model, unwrapped)
  end
end

def retry_delay(headers, retry_count:)

Returns:
  • (Float) -

Parameters:
  • retry_count (Integer) --
  • headers (Hash{String=>String}) --

Other tags:
    Api: - private
def retry_delay(headers, retry_count:)
standard extension
 Float(headers["retry-after-ms"], exception: false)&.then { _1 / 1000 }
 span if span
header = headers["retry-after"]
 span if (span = Float(retry_header, exception: false))
 retry_header&.then do
.httpdate(_1) - Time.now
 ArgumentError
 span if span
= retry_count**2
 = 1 - (0.25 * rand)
ial_retry_delay * scale * jitter).clamp(0, @max_retry_delay)

def send_request(request, redirect_count:, retry_count:, send_retry_header:)

Returns:
  • (Array(Integer, Net::HTTPResponse, Enumerable)) -

Raises:
  • (Anthropic::Errors::APIError) -

Parameters:
  • send_retry_header (Boolean) --
  • retry_count (Integer) --
  • redirect_count (Integer) --
  • request (Hash{Symbol=>Object}) -- .

Options Hash: (**request)
  • :timeout (Float) --
  • :max_retries (Integer) --
  • :body (Object) --
  • :headers (Hash{String=>String}) --
  • :url (URI::Generic) --
  • :method (Symbol) --

Other tags:
    Api: - private
def send_request(request, redirect_count:, retry_count:, send_retry_header:)
eaders, max_retries, timeout = request.fetch_values(:url, :headers, :max_retries, :timeout)
= {**request.except(:timeout), deadline: Anthropic::Internal::Util.monotonic_secs + timeout}
d_retry_header
ers["x-stainless-retry-count"] = retry_count.to_s
us, response, stream = @requester.execute(input)
 Anthropic::Errors::APIConnectionError => e
us = e
tatus
99
tus, response, stream]
..399 if redirect_count >= self.class::MAX_REDIRECTS
.class.reap_connection!(status, stream: stream)
age = "Failed to complete the request within #{self.class::MAX_REDIRECTS} redirects."
e Anthropic::Errors::APIConnectionError.new(url: url, message: message)
..399
.class.reap_connection!(status, stream: stream)
est = self.class.follow_redirect(request, status: status, response_headers: response)
_request(
quest,
direct_count: redirect_count + 1,
try_count: retry_count,
nd_retry_header: send_retry_header
hropic::Errors::APIConnectionError if retry_count >= max_retries
e status
0..) if retry_count >= max_retries || !self.class.should_retry?(status, headers: response)
ded = Kernel.then do
thropic::Internal::Util.decode_content(response, stream: stream, suppress_error: true)
re
lf.class.reap_connection!(status, stream: stream)
e Anthropic::Errors::APIStatusError.for(
l: url,
atus: status,
dy: decoded,
quest: nil,
sponse: response
0..) | Anthropic::Errors::APIConnectionError
.class.reap_connection!(status, stream: stream)
y = retry_delay(response || {}, retry_count: retry_count)
p(delay)
_request(
quest,
direct_count: redirect_count,
try_count: retry_count + 1,
nd_retry_header: send_retry_header

def should_retry?(status, headers:)

Returns:
  • (Boolean) -

Parameters:
  • headers (Hash{String=>String}, Net::HTTPHeader) --
  • status (Integer) --

Other tags:
    Api: - private
def should_retry?(status, headers:)
  coerced = Anthropic::Internal::Util.coerce_boolean(headers["x-should-retry"])
  case [coerced, status]
  in [true | false, _]
    coerced
  in [_, 408 | 409 | 429 | (500..)]
    # retry on:
    # 408: timeouts
    # 409: locks
    # 429: rate limits
    # 500+: unknown errors
    true
  else
    false
  end
end

def validate!(req)

Raises:
  • (ArgumentError) -

Parameters:
  • req (Hash{Symbol=>Object}) --

Other tags:
    Api: - private
def validate!(req)
  keys = [:method, :path, :query, :headers, :body, :unwrap, :page, :stream, :model, :options]
  case req
  in Hash
    req.each_key do |k|
      unless keys.include?(k)
        raise ArgumentError.new("Request `req` keys must be one of #{keys}, got #{k.inspect}")
      end
    end
  else
    raise ArgumentError.new("Request `req` must be a Hash or RequestOptions, got #{req.inspect}")
  end
end