savvy_openrouter 0.2.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/.rspec +3 -0
- data/.rubocop.yml +50 -0
- data/CHANGELOG.md +23 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +234 -0
- data/Rakefile +12 -0
- data/exe/savvy_openrouter +32 -0
- data/lib/generators/savvy_openrouter/install/install_generator.rb +17 -0
- data/lib/generators/savvy_openrouter/install/templates/savvy_openrouter.yml +53 -0
- data/lib/savvy_openrouter/api_call_logger.rb +93 -0
- data/lib/savvy_openrouter/client.rb +105 -0
- data/lib/savvy_openrouter/completion_retry_policy.rb +135 -0
- data/lib/savvy_openrouter/configuration.rb +156 -0
- data/lib/savvy_openrouter/connection.rb +316 -0
- data/lib/savvy_openrouter/connection_instrumentation.rb +133 -0
- data/lib/savvy_openrouter/errors.rb +35 -0
- data/lib/savvy_openrouter/resources/analytics.rb +13 -0
- data/lib/savvy_openrouter/resources/anthropic_messages.rb +14 -0
- data/lib/savvy_openrouter/resources/api_keys.rb +33 -0
- data/lib/savvy_openrouter/resources/audio.rb +19 -0
- data/lib/savvy_openrouter/resources/base.rb +23 -0
- data/lib/savvy_openrouter/resources/chat.rb +45 -0
- data/lib/savvy_openrouter/resources/credits.rb +13 -0
- data/lib/savvy_openrouter/resources/embeddings.rb +18 -0
- data/lib/savvy_openrouter/resources/endpoints.rb +13 -0
- data/lib/savvy_openrouter/resources/generations.rb +17 -0
- data/lib/savvy_openrouter/resources/guardrails.rb +61 -0
- data/lib/savvy_openrouter/resources/models.rb +57 -0
- data/lib/savvy_openrouter/resources/oauth.rb +17 -0
- data/lib/savvy_openrouter/resources/organization.rb +13 -0
- data/lib/savvy_openrouter/resources/providers.rb +13 -0
- data/lib/savvy_openrouter/resources/rerank.rb +14 -0
- data/lib/savvy_openrouter/resources/responses.rb +14 -0
- data/lib/savvy_openrouter/resources/videos.rb +53 -0
- data/lib/savvy_openrouter/resources/workspaces.rb +37 -0
- data/lib/savvy_openrouter/streaming.rb +32 -0
- data/lib/savvy_openrouter/version.rb +5 -0
- data/lib/savvy_openrouter.rb +13 -0
- data/savvy_openrouter-0.1.0.gem +0 -0
- data/sig/savvy_openrouter.rbs +150 -0
- metadata +165 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module SavvyOpenrouter
|
|
6
|
+
module Resources
|
|
7
|
+
class Guardrails < Base
|
|
8
|
+
def list(**params)
|
|
9
|
+
conn.get("/guardrails", params: params)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def create(**body)
|
|
13
|
+
conn.post("/guardrails", body: body, success: [201])
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def get(id)
|
|
17
|
+
conn.get("/guardrails/#{id}")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def update(id, **body)
|
|
21
|
+
conn.patch("/guardrails/#{id}", body: body)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def delete(id)
|
|
25
|
+
conn.delete("/guardrails/#{id}")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def list_key_assignments(guardrail_id, **params)
|
|
29
|
+
conn.get("/guardrails/#{guardrail_id}/assignments/keys", params: params)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def bulk_assign_keys(guardrail_id, **body)
|
|
33
|
+
conn.post("/guardrails/#{guardrail_id}/assignments/keys", body: body)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def bulk_unassign_keys(guardrail_id, **body)
|
|
37
|
+
conn.post("/guardrails/#{guardrail_id}/assignments/keys/remove", body: body)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def list_member_assignments(guardrail_id, **params)
|
|
41
|
+
conn.get("/guardrails/#{guardrail_id}/assignments/members", params: params)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def bulk_assign_members(guardrail_id, **body)
|
|
45
|
+
conn.post("/guardrails/#{guardrail_id}/assignments/members", body: body)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def bulk_unassign_members(guardrail_id, **body)
|
|
49
|
+
conn.post("/guardrails/#{guardrail_id}/assignments/members/remove", body: body)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def list_all_key_assignments(**params)
|
|
53
|
+
conn.get("/guardrails/assignments/keys", params: params)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def list_all_member_assignments(**params)
|
|
57
|
+
conn.get("/guardrails/assignments/members", params: params)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module SavvyOpenrouter
|
|
6
|
+
module Resources
|
|
7
|
+
class Models < Base
|
|
8
|
+
# Query params match OpenRouter GET /models (e.g. category, output_modalities, supported_parameters).
|
|
9
|
+
def list(**params)
|
|
10
|
+
query = stringify_query(params)
|
|
11
|
+
conn.get("/models", params: query.empty? ? nil : query)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Uses GET /models with filters, then returns the first model whose prompt + completion pricing are zero.
|
|
15
|
+
# OpenRouter returns models in curated rank order within a category; first matching free model aligns with
|
|
16
|
+
# site “top free” picks when combined with output_modalities=text.
|
|
17
|
+
def first_ranked_free_text_model(category:, output_modalities: "text")
|
|
18
|
+
res = list(category: category, output_modalities: output_modalities)
|
|
19
|
+
data = res[:data] || []
|
|
20
|
+
data.find { |m| free_pricing?(m) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def count
|
|
24
|
+
conn.get("/models/count")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def user
|
|
28
|
+
conn.get("/models/user")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def endpoints(author:, slug:)
|
|
32
|
+
conn.get("/models/#{author}/#{slug}/endpoints")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def stringify_query(params)
|
|
38
|
+
params.each_with_object({}) do |(k, v), acc|
|
|
39
|
+
next if v.nil?
|
|
40
|
+
|
|
41
|
+
acc[k.to_s] = v
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def free_pricing?(model)
|
|
46
|
+
p = model[:pricing] || {}
|
|
47
|
+
zero_price?(p[:prompt]) && zero_price?(p[:completion])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def zero_price?(value)
|
|
51
|
+
return false if value.nil?
|
|
52
|
+
|
|
53
|
+
value.to_s.gsub(/[^\d.\-eE]/, "").to_f.zero?
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module SavvyOpenrouter
|
|
6
|
+
module Resources
|
|
7
|
+
class OAuth < Base
|
|
8
|
+
def exchange(**body)
|
|
9
|
+
conn.post("/auth/keys", body: body)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def create_auth_code(**body)
|
|
13
|
+
conn.post("/auth/keys/code", body: body)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module SavvyOpenrouter
|
|
6
|
+
module Resources
|
|
7
|
+
class Responses < Base
|
|
8
|
+
def create(**params)
|
|
9
|
+
body = config.merge_responses_body(params)
|
|
10
|
+
conn.post("/responses", body: body)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module SavvyOpenrouter
|
|
6
|
+
module Resources
|
|
7
|
+
class Videos < Base
|
|
8
|
+
TERMINAL_STATUSES = %w[completed failed cancelled expired].freeze
|
|
9
|
+
|
|
10
|
+
def create(**params)
|
|
11
|
+
body = config.merge_video_body(params)
|
|
12
|
+
conn.post("/videos", body: body)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
alias submit create
|
|
16
|
+
|
|
17
|
+
def get(job_id)
|
|
18
|
+
conn.get("/videos/#{job_id}")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
alias retrieve get
|
|
22
|
+
alias poll get
|
|
23
|
+
|
|
24
|
+
def download(job_id, index: 0)
|
|
25
|
+
conn.get_raw("/videos/#{job_id}/content", params: { index: index })
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def stream(job_id, index: 0, &block)
|
|
29
|
+
raise ArgumentError, "block required" unless block
|
|
30
|
+
|
|
31
|
+
conn.stream_get("/videos/#{job_id}/content", params: { index: index }, &block)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def poll_until(job_id, interval: 2, timeout: 600)
|
|
35
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
36
|
+
loop do
|
|
37
|
+
res = get(job_id)
|
|
38
|
+
st = res[:status].to_s
|
|
39
|
+
return res if TERMINAL_STATUSES.include?(st)
|
|
40
|
+
|
|
41
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
42
|
+
raise TimeoutPollError, "Timed out waiting for video job #{job_id}" if now > deadline
|
|
43
|
+
|
|
44
|
+
sleep(interval)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def models
|
|
49
|
+
conn.get("/videos/models")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module SavvyOpenrouter
|
|
6
|
+
module Resources
|
|
7
|
+
class Workspaces < Base
|
|
8
|
+
def list(**params)
|
|
9
|
+
conn.get("/workspaces", params: params)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def create(**body)
|
|
13
|
+
conn.post("/workspaces", body: body, success: [201])
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def get(id)
|
|
17
|
+
conn.get("/workspaces/#{id}")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def update(id, **body)
|
|
21
|
+
conn.patch("/workspaces/#{id}", body: body)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def delete(id)
|
|
25
|
+
conn.delete("/workspaces/#{id}")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def bulk_add_members(workspace_id, **body)
|
|
29
|
+
conn.post("/workspaces/#{workspace_id}/members/add", body: body)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def bulk_remove_members(workspace_id, **body)
|
|
33
|
+
conn.post("/workspaces/#{workspace_id}/members/remove", body: body)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SavvyOpenrouter
|
|
4
|
+
module Streaming
|
|
5
|
+
extend self
|
|
6
|
+
|
|
7
|
+
# Yields each SSE `data:` payload line (without `data:` prefix), skipping `[DONE]`.
|
|
8
|
+
def each_sse_data(chunk_enum, &block)
|
|
9
|
+
buffer = +""
|
|
10
|
+
chunk_enum.each do |chunk|
|
|
11
|
+
buffer << chunk
|
|
12
|
+
flush_sse!(buffer, &block)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def flush_sse!(buffer, &block)
|
|
19
|
+
while (idx = buffer.index("\n\n"))
|
|
20
|
+
blob = buffer.slice!(0..(idx + 1))
|
|
21
|
+
blob.each_line do |line|
|
|
22
|
+
line = line.strip
|
|
23
|
+
next if line.empty?
|
|
24
|
+
next unless line.start_with?("data:")
|
|
25
|
+
|
|
26
|
+
payload = line.sub(/\Adata:\s*/, "")
|
|
27
|
+
block.call(payload) unless payload == "[DONE]"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "savvy_openrouter/version"
|
|
4
|
+
require_relative "savvy_openrouter/errors"
|
|
5
|
+
require_relative "savvy_openrouter/configuration"
|
|
6
|
+
require_relative "savvy_openrouter/completion_retry_policy"
|
|
7
|
+
require_relative "savvy_openrouter/api_call_logger"
|
|
8
|
+
require_relative "savvy_openrouter/connection"
|
|
9
|
+
require_relative "savvy_openrouter/streaming"
|
|
10
|
+
require_relative "savvy_openrouter/client"
|
|
11
|
+
|
|
12
|
+
module SavvyOpenrouter
|
|
13
|
+
end
|
|
Binary file
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
module SavvyOpenrouter
|
|
2
|
+
VERSION: String
|
|
3
|
+
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
attr_reader status_code: Integer?
|
|
6
|
+
attr_reader response_body: untyped
|
|
7
|
+
def initialize: (?String message, ?status_code: Integer?, ?response_body: untyped) -> void
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class ConfigurationError < Error
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class ApiError < Error
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class BadRequestError < ApiError
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class AuthenticationError < ApiError
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class PaymentRequiredError < ApiError
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class ForbiddenError < ApiError
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class NotFoundError < ApiError
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class RateLimitError < ApiError
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class InternalServerError < ApiError
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class BadGatewayError < ApiError
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class TimeoutPollError < Error
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class Configuration
|
|
44
|
+
attr_accessor api_key: String?
|
|
45
|
+
attr_accessor base_url: String
|
|
46
|
+
attr_accessor default_model: String?
|
|
47
|
+
attr_accessor http_referer: String?
|
|
48
|
+
attr_accessor app_title: String?
|
|
49
|
+
attr_reader defaults: Hash[String, untyped]
|
|
50
|
+
attr_reader video_defaults: Hash[String, untyped]
|
|
51
|
+
attr_reader responses_defaults: Hash[String, untyped]
|
|
52
|
+
attr_reader api_call_log: Hash[String, untyped]
|
|
53
|
+
attr_reader chat_retries: Hash[String, untyped]
|
|
54
|
+
|
|
55
|
+
def initialize: (?config_path: String?, **untyped options) -> void
|
|
56
|
+
def merge_chat_body: (Hash[Symbol | String, untyped] body) -> Hash[String, untyped]
|
|
57
|
+
def merge_video_body: (Hash[Symbol | String, untyped] body) -> Hash[String, untyped]
|
|
58
|
+
def merge_responses_body: (Hash[Symbol | String, untyped] body) -> Hash[String, untyped]
|
|
59
|
+
def self.load_file: (String path) -> Hash[String, untyped]
|
|
60
|
+
def self.discover_config_file: (?String cwd) -> String?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class ApiCallLogger
|
|
64
|
+
def initialize: (Hash[String, untyped] config) -> void
|
|
65
|
+
def enabled?: () -> bool
|
|
66
|
+
def max_body_limit: () -> Integer
|
|
67
|
+
def record: (Hash[String, untyped] attrs) -> untyped
|
|
68
|
+
def self.format_body_for_log: (untyped obj, ?max_bytes: Integer) -> String
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class CompletionRetryPolicy
|
|
72
|
+
def initialize: (Hash[String, untyped] | false? raw) -> void
|
|
73
|
+
def max_attempts: () -> Integer
|
|
74
|
+
def enabled?: () -> bool
|
|
75
|
+
def retry_http_error?: (ApiError error) -> bool
|
|
76
|
+
def retry_response?: (Hash[Symbol | String, untyped] response) -> bool
|
|
77
|
+
def wait_after_attempt: (Integer attempt_number) -> void
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class Connection
|
|
81
|
+
DEFAULT_SUCCESS: Array[Integer]
|
|
82
|
+
|
|
83
|
+
attr_reader config: Configuration
|
|
84
|
+
|
|
85
|
+
def initialize: (Configuration config) -> void
|
|
86
|
+
def get: (String path, ?params: Hash[Symbol | String, untyped]?, ?success: Array[Integer]?) -> untyped
|
|
87
|
+
def delete: (String path, ?params: Hash[Symbol | String, untyped]?, ?success: Array[Integer]?) -> untyped
|
|
88
|
+
def post: (String path, body: Hash[Symbol | String, untyped], ?success: Array[Integer]?) -> untyped
|
|
89
|
+
def patch: (String path, body: Hash[Symbol | String, untyped], ?success: Array[Integer]?) -> untyped
|
|
90
|
+
def put: (String path, body: Hash[Symbol | String, untyped], ?success: Array[Integer]?) -> untyped
|
|
91
|
+
def get_raw: (String path, ?params: Hash[Symbol | String, untyped]?, ?success: Array[Integer]?) -> String
|
|
92
|
+
def post_raw: (String path, body: Hash[Symbol | String, untyped], ?success: Array[Integer]?) -> String
|
|
93
|
+
def stream_get: (String path, ?params: Hash[Symbol | String, untyped]?) { (String chunk) -> void } -> void
|
|
94
|
+
def stream_post: (String path, Hash[Symbol | String, untyped] body) { (String chunk) -> void } -> void
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
class Client
|
|
98
|
+
attr_reader config: Configuration
|
|
99
|
+
attr_reader connection: Connection
|
|
100
|
+
|
|
101
|
+
def initialize: (?config_path: String?, **untyped options) -> void
|
|
102
|
+
|
|
103
|
+
def chat: -> Resources::Chat
|
|
104
|
+
def responses: -> Resources::Responses
|
|
105
|
+
def anthropic_messages: -> Resources::AnthropicMessages
|
|
106
|
+
def embeddings: -> Resources::Embeddings
|
|
107
|
+
def rerank: -> Resources::Rerank
|
|
108
|
+
def models: -> Resources::Models
|
|
109
|
+
def credits: -> Resources::Credits
|
|
110
|
+
def providers: -> Resources::Providers
|
|
111
|
+
def generations: -> Resources::Generations
|
|
112
|
+
def endpoints: -> Resources::Endpoints
|
|
113
|
+
def analytics: -> Resources::Analytics
|
|
114
|
+
def audio: -> Resources::Audio
|
|
115
|
+
def videos: -> Resources::Videos
|
|
116
|
+
def oauth: -> Resources::OAuth
|
|
117
|
+
def api_keys: -> Resources::ApiKeys
|
|
118
|
+
def organization: -> Resources::Organization
|
|
119
|
+
def guardrails: -> Resources::Guardrails
|
|
120
|
+
def workspaces: -> Resources::Workspaces
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
module Resources
|
|
124
|
+
class Chat
|
|
125
|
+
def completions: (messages: untyped, **untyped params) -> untyped
|
|
126
|
+
def completions_stream: (messages: untyped, **untyped params) ?{ (String sse_data_line) -> void } -> (Enumerator[String] | void)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
class Models
|
|
130
|
+
def list: (**untyped params) -> untyped
|
|
131
|
+
def count: () -> untyped
|
|
132
|
+
def user: () -> untyped
|
|
133
|
+
def endpoints: (author: String, slug: String) -> untyped
|
|
134
|
+
def first_ranked_free_text_model: (category: String, ?output_modalities: String) -> Hash[Symbol, untyped]?
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
class Videos
|
|
138
|
+
def create: (**untyped params) -> untyped
|
|
139
|
+
def get: (String job_id) -> untyped
|
|
140
|
+
def download: (String job_id, ?index: Integer) -> String
|
|
141
|
+
def stream: (String job_id, ?index: Integer) { (String chunk) -> void } -> void
|
|
142
|
+
def poll_until: (String job_id, ?interval: Numeric, ?timeout: Numeric) -> untyped
|
|
143
|
+
def models: () -> untyped
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
module Streaming
|
|
148
|
+
def self.each_sse_data: (Enumerator[String] chunk_enum) { (String payload) -> void } -> void
|
|
149
|
+
end
|
|
150
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: savvy_openrouter
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Bryan Feller
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-05-10 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: faraday
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '4'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '2.0'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '4'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: rake
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - "~>"
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '13.0'
|
|
39
|
+
type: :development
|
|
40
|
+
prerelease: false
|
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - "~>"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '13.0'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: rspec
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - "~>"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '3.0'
|
|
53
|
+
type: :development
|
|
54
|
+
prerelease: false
|
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - "~>"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '3.0'
|
|
60
|
+
- !ruby/object:Gem::Dependency
|
|
61
|
+
name: rubocop
|
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - "~>"
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '1.21'
|
|
67
|
+
type: :development
|
|
68
|
+
prerelease: false
|
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - "~>"
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '1.21'
|
|
74
|
+
- !ruby/object:Gem::Dependency
|
|
75
|
+
name: webmock
|
|
76
|
+
requirement: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - "~>"
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: '3.23'
|
|
81
|
+
type: :development
|
|
82
|
+
prerelease: false
|
|
83
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - "~>"
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '3.23'
|
|
88
|
+
description: 'OpenRouter Ruby client: chat (streaming SSE), configurable retries for
|
|
89
|
+
non-streaming completions, optional DB-backed API call logging, Responses API defaults,
|
|
90
|
+
models listing with filters and free-tier hints, embeddings, rerank, audio, video,
|
|
91
|
+
OAuth, API keys, guardrails, workspaces, and related REST endpoints.'
|
|
92
|
+
email:
|
|
93
|
+
- brizzlefeller@gmail.com
|
|
94
|
+
executables:
|
|
95
|
+
- savvy_openrouter
|
|
96
|
+
extensions: []
|
|
97
|
+
extra_rdoc_files: []
|
|
98
|
+
files:
|
|
99
|
+
- ".rspec"
|
|
100
|
+
- ".rubocop.yml"
|
|
101
|
+
- CHANGELOG.md
|
|
102
|
+
- CODE_OF_CONDUCT.md
|
|
103
|
+
- LICENSE.txt
|
|
104
|
+
- README.md
|
|
105
|
+
- Rakefile
|
|
106
|
+
- exe/savvy_openrouter
|
|
107
|
+
- lib/generators/savvy_openrouter/install/install_generator.rb
|
|
108
|
+
- lib/generators/savvy_openrouter/install/templates/savvy_openrouter.yml
|
|
109
|
+
- lib/savvy_openrouter.rb
|
|
110
|
+
- lib/savvy_openrouter/api_call_logger.rb
|
|
111
|
+
- lib/savvy_openrouter/client.rb
|
|
112
|
+
- lib/savvy_openrouter/completion_retry_policy.rb
|
|
113
|
+
- lib/savvy_openrouter/configuration.rb
|
|
114
|
+
- lib/savvy_openrouter/connection.rb
|
|
115
|
+
- lib/savvy_openrouter/connection_instrumentation.rb
|
|
116
|
+
- lib/savvy_openrouter/errors.rb
|
|
117
|
+
- lib/savvy_openrouter/resources/analytics.rb
|
|
118
|
+
- lib/savvy_openrouter/resources/anthropic_messages.rb
|
|
119
|
+
- lib/savvy_openrouter/resources/api_keys.rb
|
|
120
|
+
- lib/savvy_openrouter/resources/audio.rb
|
|
121
|
+
- lib/savvy_openrouter/resources/base.rb
|
|
122
|
+
- lib/savvy_openrouter/resources/chat.rb
|
|
123
|
+
- lib/savvy_openrouter/resources/credits.rb
|
|
124
|
+
- lib/savvy_openrouter/resources/embeddings.rb
|
|
125
|
+
- lib/savvy_openrouter/resources/endpoints.rb
|
|
126
|
+
- lib/savvy_openrouter/resources/generations.rb
|
|
127
|
+
- lib/savvy_openrouter/resources/guardrails.rb
|
|
128
|
+
- lib/savvy_openrouter/resources/models.rb
|
|
129
|
+
- lib/savvy_openrouter/resources/oauth.rb
|
|
130
|
+
- lib/savvy_openrouter/resources/organization.rb
|
|
131
|
+
- lib/savvy_openrouter/resources/providers.rb
|
|
132
|
+
- lib/savvy_openrouter/resources/rerank.rb
|
|
133
|
+
- lib/savvy_openrouter/resources/responses.rb
|
|
134
|
+
- lib/savvy_openrouter/resources/videos.rb
|
|
135
|
+
- lib/savvy_openrouter/resources/workspaces.rb
|
|
136
|
+
- lib/savvy_openrouter/streaming.rb
|
|
137
|
+
- lib/savvy_openrouter/version.rb
|
|
138
|
+
- savvy_openrouter-0.1.0.gem
|
|
139
|
+
- sig/savvy_openrouter.rbs
|
|
140
|
+
homepage: https://github.com/brizzlefeller/savvy_openrouter
|
|
141
|
+
licenses:
|
|
142
|
+
- MIT
|
|
143
|
+
metadata:
|
|
144
|
+
homepage_uri: https://github.com/brizzlefeller/savvy_openrouter
|
|
145
|
+
source_code_uri: https://github.com/brizzlefeller/savvy_openrouter
|
|
146
|
+
changelog_uri: https://github.com/brizzlefeller/savvy_openrouter/blob/master/CHANGELOG.md
|
|
147
|
+
rubygems_mfa_required: 'true'
|
|
148
|
+
rdoc_options: []
|
|
149
|
+
require_paths:
|
|
150
|
+
- lib
|
|
151
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
152
|
+
requirements:
|
|
153
|
+
- - ">="
|
|
154
|
+
- !ruby/object:Gem::Version
|
|
155
|
+
version: 3.1.0
|
|
156
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
157
|
+
requirements:
|
|
158
|
+
- - ">="
|
|
159
|
+
- !ruby/object:Gem::Version
|
|
160
|
+
version: '0'
|
|
161
|
+
requirements: []
|
|
162
|
+
rubygems_version: 3.6.2
|
|
163
|
+
specification_version: 4
|
|
164
|
+
summary: Ruby client for the OpenRouter unified AI API.
|
|
165
|
+
test_files: []
|