openrouter_client 0.1.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.
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenRouter
4
+ # Represents generation metadata and statistics from the OpenRouter API.
5
+ # Provides access to detailed usage information, cost, and timing data.
6
+ class Generation
7
+ GENERATION_PATH = "/generation"
8
+
9
+ # @return [String] The generation identifier
10
+ attr_reader :id
11
+ # @return [String, nil] The model used
12
+ attr_reader :model
13
+ # @return [Integer, nil] Total generation time in milliseconds
14
+ attr_reader :total_time
15
+ # @return [Integer, nil] Time to first token in milliseconds
16
+ attr_reader :time_to_first_token
17
+ # @return [Integer, nil] Native prompt tokens (from model's tokenizer)
18
+ attr_reader :native_prompt_tokens
19
+ # @return [Integer, nil] Native completion tokens (from model's tokenizer)
20
+ attr_reader :native_completion_tokens
21
+ # @return [Integer, nil] Native total tokens
22
+ attr_reader :native_total_tokens
23
+ # @return [Float, nil] Total cost in USD
24
+ attr_reader :total_cost
25
+ # @return [String, nil] Provider that served the request
26
+ attr_reader :provider
27
+ # @return [String, nil] Generation status
28
+ attr_reader :status
29
+ # @return [Hash, nil] Raw response data
30
+ attr_reader :raw
31
+
32
+ # @param attributes [Hash] Raw attributes from OpenRouter API
33
+ # @param client [OpenRouter::Client] HTTP client
34
+ def initialize(attributes, client: OpenRouter.client)
35
+ @client = client
36
+ @raw = attributes
37
+ reset_attributes(attributes)
38
+ end
39
+
40
+ class << self
41
+ # Find generation metadata by ID.
42
+ # @param id [String] The generation identifier (from completion response)
43
+ # @param client [OpenRouter::Client] HTTP client
44
+ # @return [OpenRouter::Generation, nil]
45
+ def find_by(id:, client: OpenRouter.client)
46
+ response = client.get(GENERATION_PATH, query: { id: id })
47
+ return nil unless response && response["data"]
48
+
49
+ new(response["data"], client: client)
50
+ end
51
+ end
52
+
53
+ # Get the cost per prompt token.
54
+ # @return [Float, nil]
55
+ def prompt_cost
56
+ return nil unless @total_cost && @native_prompt_tokens && @native_completion_tokens
57
+ return nil if @native_total_tokens.nil? || @native_total_tokens.zero?
58
+
59
+ # Estimate prompt cost based on typical pricing ratios
60
+ @total_cost * (@native_prompt_tokens.to_f / @native_total_tokens)
61
+ end
62
+
63
+ # Get the cost per completion token.
64
+ # @return [Float, nil]
65
+ def completion_cost
66
+ return nil unless @total_cost && @native_prompt_tokens && @native_completion_tokens
67
+ return nil if @native_total_tokens.nil? || @native_total_tokens.zero?
68
+
69
+ @total_cost * (@native_completion_tokens.to_f / @native_total_tokens)
70
+ end
71
+
72
+ # Get tokens per second for generation.
73
+ # @return [Float, nil]
74
+ def tokens_per_second
75
+ return nil unless @native_completion_tokens && @total_time&.positive?
76
+
77
+ @native_completion_tokens.to_f / (@total_time / 1000.0)
78
+ end
79
+
80
+ # Check if the generation is complete.
81
+ # @return [Boolean]
82
+ def complete?
83
+ @status == "complete" || @status == "completed"
84
+ end
85
+
86
+ # Check if the generation failed.
87
+ # @return [Boolean]
88
+ def failed?
89
+ @status == "failed" || @status == "error"
90
+ end
91
+
92
+ private
93
+
94
+ def reset_attributes(attributes)
95
+ @id = attributes["id"]
96
+ @model = attributes["model"]
97
+ @total_time = attributes["total_time"]
98
+ @time_to_first_token = attributes["time_to_first_token"]
99
+ @native_prompt_tokens = attributes["native_prompt_tokens"] || attributes["tokens_prompt"]
100
+ @native_completion_tokens = attributes["native_completion_tokens"] || attributes["tokens_completion"]
101
+ @native_total_tokens = (@native_prompt_tokens || 0) + (@native_completion_tokens || 0) if @native_prompt_tokens || @native_completion_tokens
102
+ @total_cost = attributes["total_cost"] || attributes["usage"]
103
+ @provider = attributes["provider_name"] || attributes["provider"]
104
+ @status = attributes["status"] || attributes["generation_status"]
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenRouter
4
+ # Represents a model available through the OpenRouter API.
5
+ # Provides helpers to list, search, and get model details.
6
+ class Model
7
+ MODELS_PATH = "/models"
8
+
9
+ # @return [String] The model identifier (e.g., "openai/gpt-4")
10
+ attr_reader :id
11
+ # @return [String, nil] Human-readable name
12
+ attr_reader :name
13
+ # @return [String, nil] Model description
14
+ attr_reader :description
15
+ # @return [Integer, nil] Context length in tokens
16
+ attr_reader :context_length
17
+ # @return [Hash, nil] Pricing information
18
+ attr_reader :pricing
19
+ # @return [Integer, nil] Top provider information
20
+ attr_reader :top_provider
21
+ # @return [String, nil] Model architecture
22
+ attr_reader :architecture
23
+ # @return [Array<String>, nil] Supported parameters
24
+ attr_reader :supported_parameters
25
+ # @return [Hash, nil] Per-request limits
26
+ attr_reader :per_request_limits
27
+ # @return [String, nil] Created timestamp
28
+ attr_reader :created
29
+
30
+ # @param attributes [Hash] Raw attributes from OpenRouter API
31
+ # @param client [OpenRouter::Client] HTTP client
32
+ def initialize(attributes, client: OpenRouter.client)
33
+ @client = client
34
+ reset_attributes(attributes)
35
+ end
36
+
37
+ # Get the input price per million tokens.
38
+ # @return [Float, nil]
39
+ def input_price
40
+ @pricing&.dig("prompt")&.to_f
41
+ end
42
+
43
+ # Get the output price per million tokens.
44
+ # @return [Float, nil]
45
+ def output_price
46
+ @pricing&.dig("completion")&.to_f
47
+ end
48
+
49
+ # Get the image input price per million tokens.
50
+ # @return [Float, nil]
51
+ def image_price
52
+ @pricing&.dig("image")&.to_f
53
+ end
54
+
55
+ # Check if the model is free to use.
56
+ # @return [Boolean]
57
+ def free?
58
+ input_price&.zero? && output_price&.zero?
59
+ end
60
+
61
+ # Run a completion using this model.
62
+ # @param messages [Array<Hash>] The messages to send
63
+ # @param options [Hash] Additional completion options
64
+ # @return [OpenRouter::Completion]
65
+ def complete(messages:, **)
66
+ OpenRouter::Completion.create!(messages: messages, model: @id, client: @client, **)
67
+ end
68
+
69
+ class << self
70
+ # Find a specific model by ID. Raises NotFoundError if not found.
71
+ # @param id [String] The model identifier
72
+ # @param client [OpenRouter::Client] HTTP client
73
+ # @return [OpenRouter::Model]
74
+ # @raise [OpenRouter::NotFoundError] if model not found
75
+ def find(id, client: OpenRouter.client)
76
+ model = find_by(id: id, client: client)
77
+ raise NotFoundError, "Model not found: #{id}" unless model
78
+
79
+ model
80
+ end
81
+
82
+ # Find a specific model by ID. Returns nil if not found.
83
+ # @param id [String] The model identifier
84
+ # @param client [OpenRouter::Client] HTTP client
85
+ # @return [OpenRouter::Model, nil]
86
+ def find_by(id:, client: OpenRouter.client)
87
+ response = client.get(MODELS_PATH)
88
+ models = Array(response && response["data"])
89
+ entry = models.find { |model| model["id"] == id }
90
+ entry ? new(entry, client: client) : nil
91
+ end
92
+
93
+ # Find a specific model by ID. Raises NotFoundError if not found.
94
+ # @param id [String] The model identifier
95
+ # @param client [OpenRouter::Client] HTTP client
96
+ # @return [OpenRouter::Model]
97
+ # @raise [OpenRouter::NotFoundError] if model not found
98
+ def find_by!(id:, client: OpenRouter.client)
99
+ find(id, client: client)
100
+ end
101
+
102
+ # Iterate through all available models.
103
+ # @param client [OpenRouter::Client] HTTP client
104
+ # @yield [OpenRouter::Model]
105
+ # @return [void]
106
+ def each(client: OpenRouter.client, &block)
107
+ response = client.get(MODELS_PATH)
108
+ models = Array(response && response["data"])
109
+ models.each { |attributes| block.call(new(attributes, client: client)) }
110
+ end
111
+
112
+ # Return an array of all models.
113
+ # @param client [OpenRouter::Client] HTTP client
114
+ # @return [Array<OpenRouter::Model>]
115
+ def all(client: OpenRouter.client)
116
+ results = []
117
+ each(client: client) { |model| results << model }
118
+ results
119
+ end
120
+
121
+ # Search models by name or description.
122
+ # @param query [String] Search query
123
+ # @param client [OpenRouter::Client] HTTP client
124
+ # @return [Array<OpenRouter::Model>]
125
+ def search(query:, client: OpenRouter.client)
126
+ all(client: client).select do |model|
127
+ model.name&.downcase&.include?(query.downcase) ||
128
+ model.description&.downcase&.include?(query.downcase) ||
129
+ model.id.downcase.include?(query.downcase)
130
+ end
131
+ end
132
+
133
+ # Find models by provider.
134
+ # @param provider [String] Provider name (e.g., "openai", "anthropic")
135
+ # @param client [OpenRouter::Client] HTTP client
136
+ # @return [Array<OpenRouter::Model>]
137
+ def by_provider(provider:, client: OpenRouter.client)
138
+ all(client: client).select { |model| model.id.start_with?("#{provider}/") }
139
+ end
140
+
141
+ # Find free models.
142
+ # @param client [OpenRouter::Client] HTTP client
143
+ # @return [Array<OpenRouter::Model>]
144
+ def free(client: OpenRouter.client)
145
+ all(client: client).select(&:free?)
146
+ end
147
+ end
148
+
149
+ private
150
+
151
+ def reset_attributes(attributes)
152
+ @id = attributes["id"]
153
+ @name = attributes["name"]
154
+ @description = attributes["description"]
155
+ @context_length = attributes["context_length"]
156
+ @pricing = attributes["pricing"]
157
+ @top_provider = attributes["top_provider"]
158
+ @architecture = attributes["architecture"]
159
+ @supported_parameters = attributes["supported_parameters"]
160
+ @per_request_limits = attributes["per_request_limits"]
161
+ @created = attributes["created"]
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenRouter
4
+ # Streaming helper for Server-Sent Events from OpenRouter API.
5
+ # It parses SSE lines and yields decoded event hashes.
6
+ class Stream
7
+ # @return [String] endpoint path
8
+ attr_reader :path
9
+
10
+ # @param path [String] API endpoint path
11
+ # @param input [Hash] request payload
12
+ # @param client [OpenRouter::Client] HTTP client
13
+ def initialize(path:, input:, client: OpenRouter.client)
14
+ @path = path
15
+ @input = input
16
+ @client = client
17
+ end
18
+
19
+ # Stream events; yields a Hash for each event data chunk. Blocks until stream ends.
20
+ # @yield [event] yields decoded event hash
21
+ # @yieldparam event [Hash]
22
+ # @return [void]
23
+ def each(&block)
24
+ buffer = ""
25
+ decoder = SSEDecoder.new
26
+
27
+ @client.post_stream(@path, @input, on_data: proc do |chunk, _total_bytes|
28
+ buffer = (buffer + chunk).gsub(/\r\n?/, "\n")
29
+ lines = buffer.split("\n", -1)
30
+ buffer = lines.pop || ""
31
+ lines.each do |line|
32
+ event = decoder.decode(line)
33
+ block.call(event) if event
34
+ end
35
+ end)
36
+ end
37
+
38
+ # Minimal SSE decoder for parsing standard server-sent event stream lines.
39
+ class SSEDecoder
40
+ def initialize
41
+ @event = ""
42
+ @data = ""
43
+ @id = nil
44
+ @retry = nil
45
+ end
46
+
47
+ # @param line [String]
48
+ # @return [Hash, nil]
49
+ def decode(line)
50
+ return flush_event if line.empty?
51
+ return if line.start_with?(":")
52
+
53
+ field, _, value = line.partition(":")
54
+ value = value.lstrip
55
+
56
+ case field
57
+ when "event"
58
+ @event = value
59
+ when "data"
60
+ @data += "#{value}\n"
61
+ when "id"
62
+ @id = value
63
+ when "retry"
64
+ @retry = value.to_i
65
+ end
66
+
67
+ nil
68
+ end
69
+
70
+ private
71
+
72
+ def flush_event
73
+ return if @data.empty?
74
+
75
+ data = @data.chomp
76
+
77
+ # Handle [DONE] message which signals end of stream
78
+ return reset_state if data == "[DONE]"
79
+
80
+ begin
81
+ parsed = JSON.parse(data)
82
+ rescue JSON::ParserError
83
+ reset_state
84
+ return nil
85
+ end
86
+
87
+ event = { "data" => parsed }
88
+ event["event"] = @event unless @event.empty?
89
+ event["id"] = @id if @id
90
+ event["retry"] = @retry if @retry
91
+
92
+ reset_state
93
+ event
94
+ end
95
+
96
+ def reset_state
97
+ @event = ""
98
+ @data = ""
99
+ @id = nil
100
+ @retry = nil
101
+ nil
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenRouter
4
+ VERSION = "0.1.0"
5
+ end
data/lib/openrouter.rb ADDED
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "time"
5
+ require "json"
6
+ require "cgi"
7
+ require "uri"
8
+
9
+ require_relative "openrouter/version"
10
+
11
+ module OpenRouter
12
+ # Base error class for all OpenRouter-related errors.
13
+ class Error < StandardError; end
14
+ # Raised when a request is unauthorized (HTTP 401).
15
+ class UnauthorizedError < Error; end
16
+ # Raised when a requested resource is not found (HTTP 404).
17
+ class NotFoundError < Error; end
18
+ # Raised when the server returns an unexpected error (HTTP 5xx or others).
19
+ class ServerError < Error; end
20
+ # Raised when the client is misconfigured.
21
+ class ConfigurationError < Error; end
22
+ # Raised when access is forbidden (HTTP 403).
23
+ class ForbiddenError < Error; end
24
+ # Raised when rate limited (HTTP 429).
25
+ class RateLimitError < Error; end
26
+ # Raised when request validation fails (HTTP 400).
27
+ class BadRequestError < Error; end
28
+ # Raised when payment is required (HTTP 402).
29
+ class PaymentRequiredError < Error; end
30
+
31
+ # Global configuration for the OpenRouter client.
32
+ class Configuration
33
+ DEFAULT_API_BASE = "https://openrouter.ai/api/v1"
34
+ DEFAULT_REQUEST_TIMEOUT = 120
35
+
36
+ # API key used for authenticating with OpenRouter endpoints.
37
+ # Defaults to ENV["OPENROUTER_API_KEY"].
38
+ # @return [String]
39
+ attr_accessor :api_key
40
+
41
+ # Base URL for OpenRouter API endpoints.
42
+ # @return [String]
43
+ attr_accessor :api_base
44
+
45
+ # Timeout in seconds for opening and processing HTTP requests.
46
+ # @return [Integer]
47
+ attr_accessor :request_timeout
48
+
49
+ # Optional HTTP-Referer header for app attribution.
50
+ # @return [String, nil]
51
+ attr_accessor :site_url
52
+
53
+ # Optional X-Title header for app attribution.
54
+ # @return [String, nil]
55
+ attr_accessor :site_name
56
+
57
+ # Initialize configuration with sensible defaults.
58
+ # @return [OpenRouter::Configuration]
59
+ def initialize
60
+ @api_key = ENV.fetch("OPENROUTER_API_KEY", nil)
61
+ @api_base = DEFAULT_API_BASE
62
+ @request_timeout = DEFAULT_REQUEST_TIMEOUT
63
+ @site_url = nil
64
+ @site_name = nil
65
+ end
66
+ end
67
+
68
+ class << self
69
+ # The global configuration instance.
70
+ # @return [OpenRouter::Configuration]
71
+ attr_accessor :configuration
72
+
73
+ # Configure the OpenRouter client.
74
+ # @yield [OpenRouter::Configuration] the configuration object to mutate
75
+ # @return [void]
76
+ def configure
77
+ self.configuration ||= Configuration.new
78
+ yield(configuration)
79
+ end
80
+
81
+ # Global client accessor using the configured settings.
82
+ # @return [OpenRouter::Client]
83
+ def client
84
+ configuration = self.configuration || Configuration.new
85
+ @client ||= OpenRouter::Client.new(configuration)
86
+ end
87
+
88
+ # Reset the client (useful for reconfiguration).
89
+ # @return [void]
90
+ def reset_client!
91
+ @client = nil
92
+ end
93
+
94
+ # Deep symbolize keys of a Hash or Array.
95
+ # @param object [Hash, Array]
96
+ # @return [Hash, Array]
97
+ def deep_symbolize_keys(object)
98
+ case object
99
+ when Hash
100
+ object.each_with_object({}) do |(key, value), result|
101
+ symbolized_key = key.is_a?(String) ? key.to_sym : key
102
+ result[symbolized_key] = deep_symbolize_keys(value)
103
+ end
104
+ when Array
105
+ object.map { |element| deep_symbolize_keys(element) }
106
+ else
107
+ object
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ require_relative "openrouter/client"
114
+ require_relative "openrouter/completion"
115
+ require_relative "openrouter/stream"
116
+ require_relative "openrouter/model"
117
+ require_relative "openrouter/generation"
118
+ require_relative "openrouter/credit"
119
+ require_relative "openrouter/api_key"
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/openrouter/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "openrouter_client"
7
+ spec.version = OpenRouter::VERSION
8
+ spec.authors = ["Dylan Player"]
9
+ spec.email = ["dylan@851.sh"]
10
+
11
+ spec.summary = "Ruby client for OpenRouter API."
12
+ spec.description = "A comprehensive Ruby client for the OpenRouter API, providing access to hundreds of AI models through a unified interface."
13
+ spec.homepage = "https://github.com/851-labs/openrouter_client"
14
+ spec.required_ruby_version = ">= 3.3.0"
15
+
16
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
20
+ spec.license = "MIT"
21
+
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
25
+ end
26
+ end
27
+ spec.bindir = "exe"
28
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_dependency("faraday", ">= 1")
32
+ spec.metadata["rubygems_mfa_required"] = "true"
33
+ end
@@ -0,0 +1,85 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module OpenRouter
5
+ class ApiKey
6
+ KEYS_PATH = T.let(T.unsafe(nil), String)
7
+ AUTH_KEY_PATH = T.let(T.unsafe(nil), String)
8
+
9
+ sig { returns(String) }
10
+ def id; end
11
+
12
+ sig { returns(T.nilable(String)) }
13
+ def name; end
14
+
15
+ sig { returns(T.nilable(String)) }
16
+ def key; end
17
+
18
+ sig { returns(T.nilable(Float)) }
19
+ def limit; end
20
+
21
+ sig { returns(T.nilable(Float)) }
22
+ def usage; end
23
+
24
+ sig { returns(T.nilable(T::Boolean)) }
25
+ def disabled; end
26
+
27
+ sig { returns(T.nilable(String)) }
28
+ def created_at; end
29
+
30
+ sig { returns(T.nilable(String)) }
31
+ def updated_at; end
32
+
33
+ sig { params(attributes: T.untyped, client: OpenRouter::Client).void }
34
+ def initialize(attributes, client: OpenRouter.client); end
35
+
36
+ class << self
37
+ sig { params(client: OpenRouter::Client).returns(OpenRouter::ApiKey) }
38
+ def current(client: OpenRouter.client); end
39
+
40
+ sig { params(client: OpenRouter::Client).returns(T::Array[OpenRouter::ApiKey]) }
41
+ def all(client: OpenRouter.client); end
42
+
43
+ sig { params(id: String, client: OpenRouter::Client).returns(T.nilable(OpenRouter::ApiKey)) }
44
+ def find_by(id:, client: OpenRouter.client); end
45
+
46
+ sig do
47
+ params(
48
+ name: String,
49
+ limit: T.nilable(Float),
50
+ client: OpenRouter::Client
51
+ ).returns(OpenRouter::ApiKey)
52
+ end
53
+ def create!(name:, limit: nil, client: OpenRouter.client); end
54
+ end
55
+
56
+ sig do
57
+ params(
58
+ name: T.nilable(String),
59
+ limit: T.nilable(Float),
60
+ disabled: T.nilable(T::Boolean)
61
+ ).returns(OpenRouter::ApiKey)
62
+ end
63
+ def update!(name: nil, limit: nil, disabled: nil); end
64
+
65
+ sig { returns(T::Boolean) }
66
+ def destroy!; end
67
+
68
+ sig { returns(T::Boolean) }
69
+ def active?; end
70
+
71
+ sig { returns(T::Boolean) }
72
+ def limited?; end
73
+
74
+ sig { returns(T::Boolean) }
75
+ def exceeded_limit?; end
76
+
77
+ sig { returns(T.nilable(Float)) }
78
+ def remaining; end
79
+
80
+ private
81
+
82
+ sig { params(attributes: T.untyped).void }
83
+ def reset_attributes(attributes); end
84
+ end
85
+ end
@@ -0,0 +1,56 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module OpenRouter
5
+ class Client
6
+ sig { returns(OpenRouter::Configuration) }
7
+ def configuration; end
8
+
9
+ sig { params(value: OpenRouter::Configuration).returns(OpenRouter::Configuration) }
10
+ def configuration=(value); end
11
+
12
+ sig { params(configuration: T.nilable(OpenRouter::Configuration)).void }
13
+ def initialize(configuration = nil); end
14
+
15
+ sig { params(path: String, payload: T.untyped, headers: T.untyped).returns(T.untyped) }
16
+ def post(path, payload = {}, headers: {}); end
17
+
18
+ sig do
19
+ params(
20
+ path: String,
21
+ payload: T.untyped,
22
+ on_data: T.proc.params(arg0: String, arg1: T.untyped).void
23
+ ).void
24
+ end
25
+ def post_stream(path, payload = {}, on_data:); end
26
+
27
+ sig { params(path: String, query: T.untyped, headers: T.untyped).returns(T.untyped) }
28
+ def get(path, query: nil, headers: {}); end
29
+
30
+ sig { params(path: String, headers: T.untyped).returns(T.untyped) }
31
+ def delete(path, headers: {}); end
32
+
33
+ sig { params(path: String, payload: T.untyped, headers: T.untyped).returns(T.untyped) }
34
+ def patch(path, payload = {}, headers: {}); end
35
+
36
+ sig { params(response: T.untyped).void }
37
+ def handle_error(response); end
38
+
39
+ private
40
+
41
+ sig { params(request: T.untyped, custom_headers: T.untyped).void }
42
+ def configure_request_headers(request, custom_headers); end
43
+
44
+ sig { params(response: T.untyped).returns(String) }
45
+ def extract_error_message(response); end
46
+
47
+ sig { params(body: T.untyped).returns(T.untyped) }
48
+ def parse_json(body); end
49
+
50
+ sig { params(path: String).returns(String) }
51
+ def build_url(path); end
52
+
53
+ sig { returns(T.untyped) }
54
+ def connection; end
55
+ end
56
+ end