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.
- checksums.yaml +7 -0
- data/.env.example +1 -0
- data/.rubocop.yml +68 -0
- data/.ruby-version +2 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +124 -0
- data/LICENSE +22 -0
- data/README.md +378 -0
- data/Rakefile +16 -0
- data/lib/openrouter/api_key.rb +136 -0
- data/lib/openrouter/client.rb +157 -0
- data/lib/openrouter/completion.rb +293 -0
- data/lib/openrouter/credit.rb +71 -0
- data/lib/openrouter/generation.rb +107 -0
- data/lib/openrouter/model.rb +164 -0
- data/lib/openrouter/stream.rb +105 -0
- data/lib/openrouter/version.rb +5 -0
- data/lib/openrouter.rb +119 -0
- data/openrouter_client.gemspec +33 -0
- data/rbi/openrouter/api_key.rbi +85 -0
- data/rbi/openrouter/client.rbi +56 -0
- data/rbi/openrouter/completion.rbi +152 -0
- data/rbi/openrouter/credit.rbi +42 -0
- data/rbi/openrouter/generation.rbi +69 -0
- data/rbi/openrouter/model.rbi +92 -0
- data/rbi/openrouter/openrouter.rbi +77 -0
- data/rbi/openrouter/stream.rbi +39 -0
- data/rbi/openrouter/version.rbi +6 -0
- data/sorbet/config +2 -0
- data/sorbet/rbi/.gitignore +2 -0
- metadata +89 -0
|
@@ -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
|
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
|