open_router_enhanced 2.0.0 → 2.0.1
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/Gemfile.lock +27 -22
- data/lib/open_router/client.rb +13 -13
- data/lib/open_router/completion_options.rb +2 -2
- data/lib/open_router/http.rb +7 -7
- data/lib/open_router/json_healer.rb +7 -0
- data/lib/open_router/model_registry.rb +15 -7
- data/lib/open_router/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d443d948a07c5b55d6366e135354b2faa07a8edc38cb2791237a6a4a92bd229a
|
|
4
|
+
data.tar.gz: 37a93b36720b58bf1ee1c4809aa4d54e4d29e06ba27a63c9285a78fa7074eb66
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 78fb6b74df5a7cb901ecac23fe503c667035e4794d6e7d9b97d4ad703e1f0a40347bd3274b86ba13c35501c8afc0b3c29ec5cb7b632eb93013af5a5328403285
|
|
7
|
+
data.tar.gz: 51d704a3035cf8211ac0d9533931d0b1a9bc9ebe49d21f192dd49b7dbb423eadcbf5adcc7b99d3212002947f2646337122e99660a589fa54be22ab3a356555eb
|
data/Gemfile.lock
CHANGED
|
@@ -11,7 +11,7 @@ PATH
|
|
|
11
11
|
GEM
|
|
12
12
|
remote: https://rubygems.org/
|
|
13
13
|
specs:
|
|
14
|
-
activesupport (8.1.
|
|
14
|
+
activesupport (8.1.3)
|
|
15
15
|
base64
|
|
16
16
|
bigdecimal
|
|
17
17
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
@@ -24,11 +24,11 @@ GEM
|
|
|
24
24
|
securerandom (>= 0.3)
|
|
25
25
|
tzinfo (~> 2.0, >= 2.0.5)
|
|
26
26
|
uri (>= 0.13.1)
|
|
27
|
-
addressable (2.
|
|
27
|
+
addressable (2.9.0)
|
|
28
28
|
public_suffix (>= 2.0.2, < 8.0)
|
|
29
29
|
ast (2.4.3)
|
|
30
30
|
base64 (0.3.0)
|
|
31
|
-
bigdecimal (4.
|
|
31
|
+
bigdecimal (4.1.1)
|
|
32
32
|
coderay (1.1.3)
|
|
33
33
|
concurrent-ruby (1.3.6)
|
|
34
34
|
connection_pool (3.0.2)
|
|
@@ -38,42 +38,47 @@ GEM
|
|
|
38
38
|
diff-lcs (1.6.2)
|
|
39
39
|
dotenv (3.2.0)
|
|
40
40
|
drb (2.2.3)
|
|
41
|
-
faraday (2.14.
|
|
41
|
+
faraday (2.14.1)
|
|
42
42
|
faraday-net_http (>= 2.0, < 3.5)
|
|
43
43
|
json
|
|
44
44
|
logger
|
|
45
|
-
faraday-multipart (1.
|
|
45
|
+
faraday-multipart (1.2.0)
|
|
46
46
|
multipart-post (~> 2.0)
|
|
47
47
|
faraday-net_http (3.4.2)
|
|
48
48
|
net-http (~> 0.5)
|
|
49
49
|
hashdiff (1.2.1)
|
|
50
50
|
i18n (1.14.8)
|
|
51
51
|
concurrent-ruby (~> 1.0)
|
|
52
|
-
|
|
52
|
+
io-console (0.8.2)
|
|
53
|
+
json (2.19.3)
|
|
53
54
|
json-schema (4.3.1)
|
|
54
55
|
addressable (>= 2.8)
|
|
55
56
|
language_server-protocol (3.17.0.5)
|
|
56
57
|
lint_roller (1.1.0)
|
|
57
58
|
logger (1.7.0)
|
|
58
59
|
method_source (1.1.0)
|
|
59
|
-
minitest (6.0.
|
|
60
|
+
minitest (6.0.4)
|
|
61
|
+
drb (~> 2.0)
|
|
60
62
|
prism (~> 1.5)
|
|
61
63
|
multipart-post (2.4.1)
|
|
62
64
|
net-http (0.9.1)
|
|
63
65
|
uri (>= 0.11.1)
|
|
64
|
-
parallel (
|
|
65
|
-
parser (3.3.
|
|
66
|
+
parallel (2.0.1)
|
|
67
|
+
parser (3.3.11.1)
|
|
66
68
|
ast (~> 2.4.1)
|
|
67
69
|
racc
|
|
68
|
-
prism (1.
|
|
69
|
-
pry (0.
|
|
70
|
+
prism (1.9.0)
|
|
71
|
+
pry (0.16.0)
|
|
70
72
|
coderay (~> 1.1)
|
|
71
73
|
method_source (~> 1.0)
|
|
72
|
-
|
|
74
|
+
reline (>= 0.6.0)
|
|
75
|
+
public_suffix (7.0.5)
|
|
73
76
|
racc (1.8.1)
|
|
74
77
|
rainbow (3.1.1)
|
|
75
|
-
rake (13.
|
|
76
|
-
regexp_parser (2.
|
|
78
|
+
rake (13.4.2)
|
|
79
|
+
regexp_parser (2.12.0)
|
|
80
|
+
reline (0.6.3)
|
|
81
|
+
io-console (~> 0.5)
|
|
77
82
|
rexml (3.4.4)
|
|
78
83
|
rspec (3.13.2)
|
|
79
84
|
rspec-core (~> 3.13.0)
|
|
@@ -84,24 +89,24 @@ GEM
|
|
|
84
89
|
rspec-expectations (3.13.5)
|
|
85
90
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
86
91
|
rspec-support (~> 3.13.0)
|
|
87
|
-
rspec-mocks (3.13.
|
|
92
|
+
rspec-mocks (3.13.8)
|
|
88
93
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
89
94
|
rspec-support (~> 3.13.0)
|
|
90
|
-
rspec-support (3.13.
|
|
91
|
-
rubocop (1.
|
|
95
|
+
rspec-support (3.13.7)
|
|
96
|
+
rubocop (1.86.1)
|
|
92
97
|
json (~> 2.3)
|
|
93
98
|
language_server-protocol (~> 3.17.0.2)
|
|
94
99
|
lint_roller (~> 1.1.0)
|
|
95
|
-
parallel (
|
|
100
|
+
parallel (>= 1.10)
|
|
96
101
|
parser (>= 3.3.0.2)
|
|
97
102
|
rainbow (>= 2.2.2, < 4.0)
|
|
98
103
|
regexp_parser (>= 2.9.3, < 3.0)
|
|
99
|
-
rubocop-ast (>= 1.
|
|
104
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
100
105
|
ruby-progressbar (~> 1.7)
|
|
101
106
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
102
|
-
rubocop-ast (1.
|
|
107
|
+
rubocop-ast (1.49.1)
|
|
103
108
|
parser (>= 3.3.7.2)
|
|
104
|
-
prism (~> 1.
|
|
109
|
+
prism (~> 1.7)
|
|
105
110
|
ruby-progressbar (1.13.0)
|
|
106
111
|
securerandom (0.4.1)
|
|
107
112
|
tzinfo (2.0.6)
|
|
@@ -111,7 +116,7 @@ GEM
|
|
|
111
116
|
unicode-emoji (4.2.0)
|
|
112
117
|
uri (1.1.1)
|
|
113
118
|
vcr (6.4.0)
|
|
114
|
-
webmock (3.26.
|
|
119
|
+
webmock (3.26.2)
|
|
115
120
|
addressable (>= 2.8.0)
|
|
116
121
|
crack (>= 0.3.2)
|
|
117
122
|
hashdiff (>= 0.4.0, < 2.0.0)
|
data/lib/open_router/client.rb
CHANGED
|
@@ -12,15 +12,19 @@ module OpenRouter
|
|
|
12
12
|
class Client
|
|
13
13
|
include OpenRouter::HTTP
|
|
14
14
|
|
|
15
|
-
attr_reader :callbacks, :usage_tracker
|
|
15
|
+
attr_reader :callbacks, :usage_tracker, :configuration
|
|
16
16
|
|
|
17
17
|
# Initializes the client with optional configurations.
|
|
18
18
|
def initialize(access_token: nil, request_timeout: nil, uri_base: nil, extra_headers: {}, track_usage: true)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
OpenRouter.configuration.
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
# Build a per-instance configuration to avoid mutating the global singleton,
|
|
20
|
+
# which would cause credential leakage across Client instances in concurrent use.
|
|
21
|
+
@configuration = OpenRouter.configuration.dup
|
|
22
|
+
@configuration.extra_headers = OpenRouter.configuration.extra_headers.dup
|
|
23
|
+
@configuration.access_token = access_token if access_token
|
|
24
|
+
@configuration.request_timeout = request_timeout if request_timeout
|
|
25
|
+
@configuration.uri_base = uri_base if uri_base
|
|
26
|
+
@configuration.extra_headers = @configuration.extra_headers.merge(extra_headers) if extra_headers.any?
|
|
27
|
+
yield(@configuration) if block_given?
|
|
24
28
|
|
|
25
29
|
# Instance-level tracking of capability warnings to avoid memory leaks
|
|
26
30
|
@capability_warnings_shown = Set.new
|
|
@@ -40,10 +44,6 @@ module OpenRouter
|
|
|
40
44
|
@usage_tracker = UsageTracker.new if @track_usage
|
|
41
45
|
end
|
|
42
46
|
|
|
43
|
-
def configuration
|
|
44
|
-
OpenRouter.configuration
|
|
45
|
-
end
|
|
46
|
-
|
|
47
47
|
# Register a callback for a specific event
|
|
48
48
|
#
|
|
49
49
|
# @param event [Symbol] The event to register for (:before_request, :after_response, :on_tool_call, :on_error, :on_stream_chunk, :on_healing)
|
|
@@ -185,7 +185,7 @@ module OpenRouter
|
|
|
185
185
|
|
|
186
186
|
parameters = { model: opts.model, input: input }
|
|
187
187
|
parameters[:reasoning] = opts.reasoning if opts.reasoning
|
|
188
|
-
parameters[:tools] = serialize_tools_for_responses(opts.tools) if opts.
|
|
188
|
+
parameters[:tools] = serialize_tools_for_responses(opts.tools) if opts.tools?
|
|
189
189
|
parameters[:tool_choice] = opts.tool_choice if opts.tool_choice
|
|
190
190
|
# Prefer max_completion_tokens over max_tokens (consistent with complete() method)
|
|
191
191
|
parameters[:max_output_tokens] = opts.max_completion_tokens || opts.max_tokens if opts.max_completion_tokens || opts.max_tokens
|
|
@@ -503,7 +503,7 @@ module OpenRouter
|
|
|
503
503
|
# @param parameters [Hash] Request parameters hash
|
|
504
504
|
# @param opts [CompletionOptions] Options object
|
|
505
505
|
def configure_tool_calling!(parameters, opts)
|
|
506
|
-
return unless opts.
|
|
506
|
+
return unless opts.tools?
|
|
507
507
|
|
|
508
508
|
warn_if_unsupported(opts.model, :function_calling, "tool calling")
|
|
509
509
|
parameters[:tools] = serialize_tools(opts.tools)
|
|
@@ -516,7 +516,7 @@ module OpenRouter
|
|
|
516
516
|
# @param opts [CompletionOptions] Options object
|
|
517
517
|
# @return [Boolean] Whether forced extraction mode is being used
|
|
518
518
|
def configure_structured_outputs!(parameters, opts)
|
|
519
|
-
return false unless opts.
|
|
519
|
+
return false unless opts.response_format?
|
|
520
520
|
|
|
521
521
|
force_extraction = determine_forced_extraction_mode(opts.model, opts.force_structured_output)
|
|
522
522
|
|
|
@@ -244,14 +244,14 @@ module OpenRouter
|
|
|
244
244
|
# Check if this options object has any tools defined
|
|
245
245
|
#
|
|
246
246
|
# @return [Boolean]
|
|
247
|
-
def
|
|
247
|
+
def tools?
|
|
248
248
|
tools.is_a?(Array) && !tools.empty?
|
|
249
249
|
end
|
|
250
250
|
|
|
251
251
|
# Check if response format is configured
|
|
252
252
|
#
|
|
253
253
|
# @return [Boolean]
|
|
254
|
-
def
|
|
254
|
+
def response_format?
|
|
255
255
|
!response_format.nil?
|
|
256
256
|
end
|
|
257
257
|
|
data/lib/open_router/http.rb
CHANGED
|
@@ -78,30 +78,30 @@ module OpenRouter
|
|
|
78
78
|
|
|
79
79
|
def conn(multipart: false)
|
|
80
80
|
Faraday.new do |f|
|
|
81
|
-
f.options[:timeout] =
|
|
81
|
+
f.options[:timeout] = configuration.request_timeout
|
|
82
82
|
f.request(:multipart) if multipart
|
|
83
83
|
# NOTE: Removed MiddlewareErrors reference - was undefined and @log_errors was never set
|
|
84
84
|
f.response :raise_error
|
|
85
85
|
f.response :json if OpenRouter::HAS_JSON_MW
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
configuration.faraday_config&.call(f)
|
|
88
88
|
end
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def uri(path:)
|
|
92
|
-
base =
|
|
93
|
-
ver =
|
|
92
|
+
base = configuration.uri_base.sub(%r{/\z}, "")
|
|
93
|
+
ver = configuration.api_version.to_s.sub(%r{^/}, "").sub(%r{/\z}, "")
|
|
94
94
|
p = path.to_s.sub(%r{^/}, "")
|
|
95
|
-
|
|
95
|
+
[base, ver, p].reject(&:empty?).join("/")
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
def headers
|
|
99
99
|
{
|
|
100
|
-
"Authorization" => "Bearer #{
|
|
100
|
+
"Authorization" => "Bearer #{configuration.access_token}",
|
|
101
101
|
"Content-Type" => "application/json",
|
|
102
102
|
"X-Title" => "OpenRouter Ruby Client",
|
|
103
103
|
"HTTP-Referer" => "https://github.com/OlympiaAI/open_router"
|
|
104
|
-
}.merge(
|
|
104
|
+
}.merge(configuration.extra_headers)
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
def multipart_parameters(parameters)
|
|
@@ -22,6 +22,11 @@ module OpenRouter
|
|
|
22
22
|
|
|
23
23
|
# Enhanced heal method that supports different healing contexts
|
|
24
24
|
def heal(raw_text, schema, context: :generic)
|
|
25
|
+
# Guard against re-entrant healing triggered by on_healing callbacks
|
|
26
|
+
raise StructuredOutputError, "Recursive healing detected — cannot heal from within a healing callback" if (Thread.current[:openrouter_heal_depth] || 0).positive?
|
|
27
|
+
|
|
28
|
+
Thread.current[:openrouter_heal_depth] = 1
|
|
29
|
+
|
|
25
30
|
candidate_json = extract_json_candidate(raw_text)
|
|
26
31
|
raise StructuredOutputError, "No JSON-like content found in the response." if candidate_json.nil?
|
|
27
32
|
|
|
@@ -53,6 +58,8 @@ module OpenRouter
|
|
|
53
58
|
# Escalate to LLM-based healing with proper context
|
|
54
59
|
candidate_json = fix_with_healer_model(candidate_json, schema, e.message, e.class, original_content, context)
|
|
55
60
|
end
|
|
61
|
+
ensure
|
|
62
|
+
Thread.current[:openrouter_heal_depth] = 0
|
|
56
63
|
end
|
|
57
64
|
|
|
58
65
|
private
|
|
@@ -15,6 +15,8 @@ module OpenRouter
|
|
|
15
15
|
CACHE_METADATA_FILE = File.join(CACHE_DIR, "cache_metadata.json")
|
|
16
16
|
MAX_CACHE_SIZE_MB = 50 # Maximum cache size in megabytes
|
|
17
17
|
|
|
18
|
+
REGISTRY_MUTEX = Mutex.new
|
|
19
|
+
|
|
18
20
|
class << self
|
|
19
21
|
# Fetch models from OpenRouter API using Faraday for consistent SSL handling
|
|
20
22
|
def fetch_models_from_api
|
|
@@ -83,9 +85,11 @@ module OpenRouter
|
|
|
83
85
|
|
|
84
86
|
# Clear local cache (both files and memory)
|
|
85
87
|
def clear_cache!
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
REGISTRY_MUTEX.synchronize do
|
|
89
|
+
FileUtils.rm_rf(CACHE_DIR) if Dir.exist?(CACHE_DIR)
|
|
90
|
+
@processed_models = nil
|
|
91
|
+
@all_models = nil
|
|
92
|
+
end
|
|
89
93
|
end
|
|
90
94
|
|
|
91
95
|
# Refresh models data from API
|
|
@@ -246,7 +250,11 @@ module OpenRouter
|
|
|
246
250
|
|
|
247
251
|
# Get all registered models (fetch from API if needed)
|
|
248
252
|
def all_models
|
|
249
|
-
@all_models
|
|
253
|
+
return @all_models if @all_models # fast path without lock
|
|
254
|
+
|
|
255
|
+
REGISTRY_MUTEX.synchronize do
|
|
256
|
+
@all_models ||= fetch_and_cache_models
|
|
257
|
+
end
|
|
250
258
|
end
|
|
251
259
|
|
|
252
260
|
# Calculate estimated cost for a request
|
|
@@ -355,9 +363,9 @@ module OpenRouter
|
|
|
355
363
|
total_size = Dir.glob(File.join(CACHE_DIR, "**/*"))
|
|
356
364
|
.select { |f| File.file?(f) }
|
|
357
365
|
.sum do |f|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
366
|
+
File.size(f)
|
|
367
|
+
rescue StandardError
|
|
368
|
+
0
|
|
361
369
|
end
|
|
362
370
|
total_size / (1024.0 * 1024.0)
|
|
363
371
|
end
|
data/lib/open_router/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: open_router_enhanced
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Eric Stiens
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-04-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|