active_harness 0.2.33 → 0.2.34
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/lib/active_harness/agent/image.rb +56 -0
- data/lib/active_harness/agent/models.rb +3 -1
- data/lib/active_harness/agent/providers.rb +17 -0
- data/lib/active_harness/agent.rb +14 -5
- data/lib/active_harness/pricing/models_dev.rb +194 -0
- data/lib/active_harness/pricing/openrouter.rb +176 -0
- data/lib/active_harness/pricing.rb +41 -204
- data/lib/active_harness/providers/base.rb +5 -1
- data/lib/active_harness/providers/images/openai.rb +73 -0
- data/lib/active_harness/providers/images/openrouter.rb +66 -0
- data/lib/active_harness.rb +5 -1
- metadata +7 -3
- data/lib/active_harness/data/models.json +0 -61458
|
@@ -1,59 +1,19 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
1
|
require "json"
|
|
4
|
-
require "net/http"
|
|
5
|
-
require "uri"
|
|
6
|
-
require "fileutils"
|
|
7
2
|
|
|
8
3
|
module ActiveHarness
|
|
9
|
-
#
|
|
10
|
-
# by ActiveHarness (files present in lib/active_harness/providers/).
|
|
11
|
-
#
|
|
12
|
-
# Data source priority:
|
|
13
|
-
# 1. {project_root}/tmp/active_harness/pricing.json — fetched cache (refreshed once per day)
|
|
14
|
-
# 2. lib/active_harness/data/models.json — bundled fallback (ships with gem)
|
|
15
|
-
#
|
|
16
|
-
# Usage:
|
|
17
|
-
#
|
|
18
|
-
# # Fetch fresh data and save to tmp cache (also called automatically when stale)
|
|
19
|
-
# ActiveHarness::Pricing.update
|
|
20
|
-
#
|
|
21
|
-
# # All models (auto-updates cache if missing or older than 24h)
|
|
22
|
-
# ActiveHarness::Pricing.all
|
|
23
|
-
#
|
|
24
|
-
# # Single model by ID
|
|
25
|
-
# ActiveHarness::Pricing.find("gpt-4o")
|
|
26
|
-
#
|
|
27
|
-
# # By provider — method or bracket syntax
|
|
28
|
-
# ActiveHarness::Pricing.providers.openai
|
|
29
|
-
# ActiveHarness::Pricing.providers[:anthropic]
|
|
4
|
+
# Pricing namespace — shared types and a facade over pricing source modules.
|
|
30
5
|
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
6
|
+
# Sources (in priority order):
|
|
7
|
+
# Pricing::OpenRouter — live data from OpenRouter API (image models, 24h cache)
|
|
8
|
+
# Pricing::ModelsDev — live data from models.dev API (all providers, 24h cache)
|
|
33
9
|
#
|
|
10
|
+
# Public facade delegates to ModelsDev (used as the general fallback):
|
|
11
|
+
# Pricing.find("gpt-4o") → ModelPrice or nil
|
|
12
|
+
# Pricing.all → Array<ModelPrice>
|
|
13
|
+
# Pricing.providers.openai → Array<ModelPrice>
|
|
14
|
+
# Pricing.update → refreshes ModelsDev cache
|
|
34
15
|
module Pricing
|
|
35
|
-
|
|
36
|
-
MODELS_DEV_URL = "https://models.dev/api.json"
|
|
37
|
-
CACHE_TTL = 86_400 # 24 hours in seconds
|
|
38
|
-
|
|
39
|
-
# Maps models.dev provider keys → ActiveHarness provider names.
|
|
40
|
-
# Only entries whose value matches a file in providers/ will be kept.
|
|
41
|
-
MODELS_DEV_PROVIDER_MAP = {
|
|
42
|
-
"openai" => "openai",
|
|
43
|
-
"anthropic" => "anthropic",
|
|
44
|
-
"google" => "gemini",
|
|
45
|
-
"google-vertex" => "vertexai",
|
|
46
|
-
"amazon-bedrock" => "bedrock",
|
|
47
|
-
"deepseek" => "deepseek",
|
|
48
|
-
"mistral" => "mistral",
|
|
49
|
-
"openrouter" => "openrouter",
|
|
50
|
-
"perplexity" => "perplexity",
|
|
51
|
-
"xai" => "xai",
|
|
52
|
-
"groq" => "groq",
|
|
53
|
-
"azure" => "azure"
|
|
54
|
-
}.freeze
|
|
55
|
-
|
|
56
|
-
# Value object representing the pricing for a single model.
|
|
16
|
+
# Pricing rates for a single model (per-million USD).
|
|
57
17
|
ModelPrice = Struct.new(
|
|
58
18
|
:id,
|
|
59
19
|
:name,
|
|
@@ -68,7 +28,7 @@ module ActiveHarness
|
|
|
68
28
|
:output_modalities,
|
|
69
29
|
keyword_init: true
|
|
70
30
|
) do
|
|
71
|
-
#
|
|
31
|
+
# Capability tags derived from modality data.
|
|
72
32
|
# Possible values: "vision", "pdf", "audio", "video", "imggen", "embed"
|
|
73
33
|
def categories
|
|
74
34
|
inp = input_modalities || []
|
|
@@ -93,201 +53,78 @@ module ActiveHarness
|
|
|
93
53
|
end
|
|
94
54
|
end
|
|
95
55
|
|
|
96
|
-
# Proxy
|
|
56
|
+
# Proxy returned by Pricing.providers — exposes providers as methods and [].
|
|
97
57
|
class ProvidersProxy
|
|
58
|
+
def initialize(source = nil)
|
|
59
|
+
@source = source
|
|
60
|
+
end
|
|
61
|
+
|
|
98
62
|
def [](name)
|
|
99
|
-
|
|
63
|
+
source.for_provider(name.to_s)
|
|
100
64
|
end
|
|
101
65
|
|
|
102
66
|
def list
|
|
103
|
-
|
|
67
|
+
source.provider_names
|
|
104
68
|
end
|
|
105
69
|
|
|
106
70
|
def method_missing(name, *args, &block)
|
|
107
71
|
provider = name.to_s
|
|
108
|
-
if
|
|
109
|
-
|
|
72
|
+
if source.provider_names.include?(provider)
|
|
73
|
+
source.for_provider(provider)
|
|
110
74
|
else
|
|
111
75
|
super
|
|
112
76
|
end
|
|
113
77
|
end
|
|
114
78
|
|
|
115
79
|
def respond_to_missing?(name, include_private = false)
|
|
116
|
-
|
|
80
|
+
source.provider_names.include?(name.to_s) || super
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def source
|
|
86
|
+
@source || ModelsDev
|
|
117
87
|
end
|
|
118
88
|
end
|
|
119
89
|
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
# Facade — delegates to ModelsDev (general fallback source)
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
120
93
|
class << self
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def all
|
|
124
|
-
ensure_fresh_registry
|
|
125
|
-
registry.map { |raw| build_cost(raw) }
|
|
94
|
+
def find(model_id)
|
|
95
|
+
ModelsDev.find(model_id)
|
|
126
96
|
end
|
|
127
97
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
ensure_fresh_registry
|
|
131
|
-
raw = registry.find { |m| m[:id] == model_id.to_s }
|
|
132
|
-
raw ? build_cost(raw) : nil
|
|
98
|
+
def all
|
|
99
|
+
ModelsDev.all
|
|
133
100
|
end
|
|
134
101
|
|
|
135
|
-
# Returns a ProvidersProxy for provider-scoped access.
|
|
136
102
|
def providers
|
|
137
|
-
|
|
103
|
+
ModelsDev.providers
|
|
138
104
|
end
|
|
139
105
|
|
|
140
|
-
# Returns pricing data for all models from the given provider.
|
|
141
106
|
def for_provider(name)
|
|
142
|
-
|
|
143
|
-
registry
|
|
144
|
-
.select { |m| m[:provider] == name.to_s }
|
|
145
|
-
.map { |m| build_cost(m) }
|
|
107
|
+
ModelsDev.for_provider(name)
|
|
146
108
|
end
|
|
147
109
|
|
|
148
|
-
# Returns a sorted list of provider names that have data.
|
|
149
110
|
def provider_names
|
|
150
|
-
|
|
151
|
-
ensure_fresh_registry
|
|
152
|
-
registry.map { |m| m[:provider] }.uniq.sort
|
|
153
|
-
end
|
|
111
|
+
ModelsDev.provider_names
|
|
154
112
|
end
|
|
155
113
|
|
|
156
|
-
# Fetches fresh pricing data from models.dev, filters to supported providers,
|
|
157
|
-
# and writes the result to {project_root}/tmp/active_harness/pricing.json.
|
|
158
|
-
# Returns the number of models saved, or raises on HTTP failure.
|
|
159
114
|
def update
|
|
160
|
-
|
|
161
|
-
models = extract_models(raw_api)
|
|
162
|
-
|
|
163
|
-
FileUtils.mkdir_p(File.dirname(cache_file))
|
|
164
|
-
File.write(cache_file, JSON.generate(models))
|
|
165
|
-
|
|
166
|
-
reload!
|
|
167
|
-
models.size
|
|
115
|
+
ModelsDev.update
|
|
168
116
|
end
|
|
169
117
|
|
|
170
|
-
# Reloads registry from disk on next access.
|
|
171
118
|
def reload!
|
|
172
|
-
|
|
173
|
-
@provider_names = nil
|
|
174
|
-
nil
|
|
119
|
+
ModelsDev.reload!
|
|
175
120
|
end
|
|
176
121
|
|
|
177
|
-
# Path to the per-project cache file.
|
|
178
122
|
def cache_file
|
|
179
|
-
|
|
123
|
+
ModelsDev.cache_file
|
|
180
124
|
end
|
|
181
125
|
|
|
182
|
-
# Names of providers supported by ActiveHarness (derived from providers/ directory).
|
|
183
126
|
def available_providers
|
|
184
|
-
|
|
185
|
-
providers_dir = File.expand_path("providers", __dir__)
|
|
186
|
-
Dir.glob("#{providers_dir}/*.rb")
|
|
187
|
-
.map { |f| File.basename(f, ".rb") }
|
|
188
|
-
.reject { |n| %w[base custom].include?(n) }
|
|
189
|
-
end
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
private
|
|
193
|
-
|
|
194
|
-
def ensure_fresh_registry
|
|
195
|
-
return if cache_file_fresh?
|
|
196
|
-
|
|
197
|
-
update
|
|
198
|
-
rescue StandardError
|
|
199
|
-
# Network unavailable or update failed — fall back to bundled/stale cache silently
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
def cache_file_fresh?
|
|
203
|
-
File.exist?(cache_file) && (Time.now - File.mtime(cache_file)) < CACHE_TTL
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
def registry
|
|
207
|
-
@registry ||= load_registry
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
def load_registry
|
|
211
|
-
if File.exist?(cache_file)
|
|
212
|
-
begin
|
|
213
|
-
data = JSON.parse(File.read(cache_file), symbolize_names: true)
|
|
214
|
-
return data if data.is_a?(Array)
|
|
215
|
-
rescue JSON::ParserError
|
|
216
|
-
# Cache file corrupted — fall through to bundled data
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
JSON.parse(File.read(BUNDLED_DATA_FILE), symbolize_names: true)
|
|
220
|
-
rescue JSON::ParserError, Errno::ENOENT
|
|
221
|
-
[]
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
def fetch_models_dev
|
|
225
|
-
uri = URI(MODELS_DEV_URL)
|
|
226
|
-
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
|
|
227
|
-
http.get(uri.request_uri)
|
|
228
|
-
end
|
|
229
|
-
raise "models.dev returned HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
|
|
230
|
-
|
|
231
|
-
JSON.parse(response.body, symbolize_names: true)
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
def extract_models(raw_api)
|
|
235
|
-
allowed = available_providers.to_set
|
|
236
|
-
|
|
237
|
-
raw_api.flat_map do |provider_key, provider_data|
|
|
238
|
-
ah_provider = MODELS_DEV_PROVIDER_MAP[provider_key.to_s]
|
|
239
|
-
next [] unless ah_provider && allowed.include?(ah_provider)
|
|
240
|
-
|
|
241
|
-
models_hash = provider_data.is_a?(Hash) ? (provider_data[:models] || {}) : {}
|
|
242
|
-
models_hash.values.filter_map do |m|
|
|
243
|
-
next unless m.is_a?(Hash) && m[:id]
|
|
244
|
-
|
|
245
|
-
cost = m[:cost] || {}
|
|
246
|
-
standard = {
|
|
247
|
-
input_per_million: cost[:input],
|
|
248
|
-
output_per_million: cost[:output],
|
|
249
|
-
cache_read_input_per_million: cost[:cache_read],
|
|
250
|
-
cache_write_input_per_million: cost[:cache_write]
|
|
251
|
-
}.compact
|
|
252
|
-
|
|
253
|
-
mods = m[:modalities] || {}
|
|
254
|
-
{
|
|
255
|
-
id: m[:id],
|
|
256
|
-
name: m[:name] || m[:id],
|
|
257
|
-
provider: ah_provider,
|
|
258
|
-
context_window: m[:context_window] || m.dig(:limit, :context),
|
|
259
|
-
max_output_tokens: m[:max_output_tokens] || m.dig(:limit, :output),
|
|
260
|
-
input_modalities: Array(mods[:input]),
|
|
261
|
-
output_modalities: Array(mods[:output]),
|
|
262
|
-
pricing: standard.any? ? { text_tokens: { standard: standard } } : {}
|
|
263
|
-
}
|
|
264
|
-
end
|
|
265
|
-
end
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
def build_cost(raw)
|
|
269
|
-
standard = raw.dig(:pricing, :text_tokens, :standard) || {}
|
|
270
|
-
ModelPrice.new(
|
|
271
|
-
id: raw[:id],
|
|
272
|
-
name: raw[:name],
|
|
273
|
-
provider: raw[:provider],
|
|
274
|
-
input_per_million: standard[:input_per_million],
|
|
275
|
-
output_per_million: standard[:output_per_million],
|
|
276
|
-
cache_read_input_per_million: standard[:cache_read_input_per_million],
|
|
277
|
-
cache_write_input_per_million: standard[:cache_write_input_per_million],
|
|
278
|
-
context_window: raw[:context_window],
|
|
279
|
-
max_output_tokens: raw[:max_output_tokens],
|
|
280
|
-
input_modalities: Array(raw[:input_modalities]),
|
|
281
|
-
output_modalities: Array(raw[:output_modalities])
|
|
282
|
-
)
|
|
283
|
-
end
|
|
284
|
-
|
|
285
|
-
def project_root
|
|
286
|
-
if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
|
|
287
|
-
Rails.root.to_s
|
|
288
|
-
else
|
|
289
|
-
Dir.pwd
|
|
290
|
-
end
|
|
127
|
+
ModelsDev.available_providers
|
|
291
128
|
end
|
|
292
129
|
end
|
|
293
130
|
end
|
|
@@ -22,14 +22,18 @@ module ActiveHarness
|
|
|
22
22
|
|
|
23
23
|
# Normalize OpenAI-compatible usage object to a consistent hash.
|
|
24
24
|
# Returns nil if the response contains no usage data.
|
|
25
|
+
# provider_cost is included when the provider returns a cost field (e.g. OpenRouter).
|
|
25
26
|
def extract_usage_openai(data)
|
|
26
27
|
u = data["usage"]
|
|
27
28
|
return nil unless u
|
|
28
|
-
|
|
29
|
+
|
|
30
|
+
result = {
|
|
29
31
|
input_tokens: u["prompt_tokens"].to_i,
|
|
30
32
|
output_tokens: u["completion_tokens"].to_i,
|
|
31
33
|
total_tokens: u["total_tokens"].to_i
|
|
32
34
|
}
|
|
35
|
+
result[:provider_cost] = u["cost"].to_f if u.key?("cost")
|
|
36
|
+
result
|
|
33
37
|
end
|
|
34
38
|
|
|
35
39
|
# Normalize Anthropic usage object.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require "uri"
|
|
2
|
+
|
|
3
|
+
module ActiveHarness
|
|
4
|
+
module Providers
|
|
5
|
+
module Images
|
|
6
|
+
class OpenAI < Base
|
|
7
|
+
ENDPOINT = "https://api.openai.com/v1/images/generations"
|
|
8
|
+
|
|
9
|
+
# @param model [String] "dall-e-2", "dall-e-3", "gpt-image-1"
|
|
10
|
+
# @param prompt [String] image description
|
|
11
|
+
# @param size [String] e.g. "1024x1024"
|
|
12
|
+
# @param quality [String] "standard"/"hd" (dall-e-3), "low"/"medium"/"high"/"auto" (gpt-image-1)
|
|
13
|
+
def call(model:, prompt:, size: "1024x1024", quality: nil, **_)
|
|
14
|
+
headers = {
|
|
15
|
+
"Content-Type" => "application/json",
|
|
16
|
+
"Authorization" => "Bearer #{api_key}"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
raw = post_json(URI(ENDPOINT), headers: headers, body: build_payload(model, prompt, size, quality), timeout: 60)
|
|
20
|
+
data = parse!(raw)
|
|
21
|
+
handle_error!(data)
|
|
22
|
+
|
|
23
|
+
b64 = data.dig("data", 0, "b64_json")
|
|
24
|
+
raise Errors::ProviderError, "No image data in response" unless b64
|
|
25
|
+
|
|
26
|
+
{ content: b64, provider: :openai, model: model, usage: nil }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def build_payload(model, prompt, size, quality)
|
|
32
|
+
payload = { model: model, prompt: prompt, n: 1, size: size }
|
|
33
|
+
# gpt-image-* always returns b64_json; older models default to url
|
|
34
|
+
payload[:response_format] = "b64_json" unless model.start_with?("gpt-image")
|
|
35
|
+
payload[:quality] = quality if quality
|
|
36
|
+
payload
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def api_key
|
|
40
|
+
key = config.openai_api_key.to_s
|
|
41
|
+
raise Errors::InvalidApiKeyError, "openai_api_key is not configured" if key.empty?
|
|
42
|
+
key
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def handle_error!(data)
|
|
46
|
+
return unless data["error"]
|
|
47
|
+
|
|
48
|
+
msg = data.dig("error", "message").to_s
|
|
49
|
+
code = data.dig("error", "code").to_s
|
|
50
|
+
type = data.dig("error", "type").to_s
|
|
51
|
+
metadata = data["error"].reject { |k, _| %w[message code type].include?(k) }
|
|
52
|
+
metadata = nil if metadata.empty?
|
|
53
|
+
|
|
54
|
+
case code
|
|
55
|
+
when "invalid_api_key", "unauthorized"
|
|
56
|
+
raise Errors::InvalidApiKeyError.new(msg, error_code: code, metadata: metadata)
|
|
57
|
+
when "rate_limit_exceeded"
|
|
58
|
+
raise Errors::RateLimitError.new(msg, error_code: code, metadata: metadata)
|
|
59
|
+
when "content_filter"
|
|
60
|
+
raise Errors::SafetyBlockedError.new(msg, error_code: code, metadata: metadata)
|
|
61
|
+
else
|
|
62
|
+
case type
|
|
63
|
+
when "server_error"
|
|
64
|
+
raise Errors::ServerError.new(msg, error_code: code, metadata: metadata)
|
|
65
|
+
else
|
|
66
|
+
raise Errors::InvalidRequestError.new(msg, error_code: code, metadata: metadata)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require "uri"
|
|
2
|
+
|
|
3
|
+
module ActiveHarness
|
|
4
|
+
module Providers
|
|
5
|
+
module Images
|
|
6
|
+
class OpenRouter < Base
|
|
7
|
+
# @param model [String] e.g. "openai/gpt-5-image-mini", "google/gemini-2.5-flash-image"
|
|
8
|
+
# @param prompt [String] image description
|
|
9
|
+
# @param size [String] ignored by OpenRouter (passed through for future support)
|
|
10
|
+
def call(model:, prompt:, size: nil, quality: nil, **_)
|
|
11
|
+
headers = {
|
|
12
|
+
"Content-Type" => "application/json",
|
|
13
|
+
"Authorization" => "Bearer #{api_key}"
|
|
14
|
+
}
|
|
15
|
+
referer = config.openrouter_http_referer.to_s
|
|
16
|
+
headers["HTTP-Referer"] = referer unless referer.empty?
|
|
17
|
+
|
|
18
|
+
messages = [{ role: "user", content: prompt }]
|
|
19
|
+
body = { model: model, messages: messages, modalities: ["image", "text"] }
|
|
20
|
+
body[:size] = size if size
|
|
21
|
+
body[:quality] = quality if quality
|
|
22
|
+
|
|
23
|
+
raw = post_json(URI(config.openrouter_api_url), headers: headers, body: body, timeout: 120)
|
|
24
|
+
data = parse!(raw)
|
|
25
|
+
handle_error!(data)
|
|
26
|
+
|
|
27
|
+
content = extract_image(data)
|
|
28
|
+
raise Errors::ProviderError, "No image data in response: #{data.dig('choices', 0, 'message')&.keys}" unless content
|
|
29
|
+
|
|
30
|
+
{ content: content, provider: :openrouter, model: model, usage: extract_usage_openai(data) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def extract_image(data)
|
|
36
|
+
images = data.dig("choices", 0, "message", "images")
|
|
37
|
+
return unless images.is_a?(Array) && images.any?
|
|
38
|
+
|
|
39
|
+
images.first&.dig("image_url", "url")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def api_key
|
|
43
|
+
key = config.openrouter_api_key.to_s
|
|
44
|
+
raise Errors::InvalidApiKeyError, "openrouter_api_key is not configured" if key.empty?
|
|
45
|
+
key
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def handle_error!(data)
|
|
49
|
+
return unless data["error"]
|
|
50
|
+
|
|
51
|
+
msg = data.dig("error", "message").to_s
|
|
52
|
+
code = data.dig("error", "code").to_s
|
|
53
|
+
metadata = data.dig("error", "metadata")
|
|
54
|
+
|
|
55
|
+
case code
|
|
56
|
+
when "401" then raise Errors::InvalidApiKeyError.new(msg, error_code: code, metadata: metadata)
|
|
57
|
+
when "402", "429" then raise Errors::RateLimitError.new(msg, error_code: code, metadata: metadata)
|
|
58
|
+
when "500", "502",
|
|
59
|
+
"503", "504" then raise Errors::ProviderUnavailableError.new(msg, error_code: code, metadata: metadata)
|
|
60
|
+
else raise Errors::InvalidRequestError.new(msg, error_code: code, metadata: metadata)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
data/lib/active_harness.rb
CHANGED
|
@@ -21,7 +21,11 @@ require_relative "active_harness/providers/azure"
|
|
|
21
21
|
require_relative "active_harness/providers/bedrock"
|
|
22
22
|
require_relative "active_harness/providers/vertexai"
|
|
23
23
|
require_relative "active_harness/providers/custom"
|
|
24
|
+
require_relative "active_harness/providers/images/openai"
|
|
25
|
+
require_relative "active_harness/providers/images/openrouter"
|
|
24
26
|
require_relative "active_harness/pricing"
|
|
27
|
+
require_relative "active_harness/pricing/models_dev"
|
|
28
|
+
require_relative "active_harness/pricing/openrouter"
|
|
25
29
|
require_relative "active_harness/memory"
|
|
26
30
|
require_relative "active_harness/agent"
|
|
27
31
|
require_relative "active_harness/tribunal"
|
|
@@ -30,7 +34,7 @@ require_relative "active_harness/pipeline"
|
|
|
30
34
|
require_relative "active_harness/railtie" if defined?(Rails::Railtie)
|
|
31
35
|
|
|
32
36
|
module ActiveHarness
|
|
33
|
-
VERSION = "0.2.
|
|
37
|
+
VERSION = "0.2.34"
|
|
34
38
|
|
|
35
39
|
class << self
|
|
36
40
|
# Configure ActiveHarness.
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active_harness
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.34
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- the-teacher
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: concurrent-ruby
|
|
@@ -36,6 +36,7 @@ files:
|
|
|
36
36
|
- lib/active_harness/agent/cost.rb
|
|
37
37
|
- lib/active_harness/agent/custom_llm_backend.rb
|
|
38
38
|
- lib/active_harness/agent/hooks.rb
|
|
39
|
+
- lib/active_harness/agent/image.rb
|
|
39
40
|
- lib/active_harness/agent/models.rb
|
|
40
41
|
- lib/active_harness/agent/output_parser.rb
|
|
41
42
|
- lib/active_harness/agent/prompt.rb
|
|
@@ -43,7 +44,6 @@ files:
|
|
|
43
44
|
- lib/active_harness/configuration.rb
|
|
44
45
|
- lib/active_harness/core/errors.rb
|
|
45
46
|
- lib/active_harness/core/hooks.rb
|
|
46
|
-
- lib/active_harness/data/models.json
|
|
47
47
|
- lib/active_harness/http/client.rb
|
|
48
48
|
- lib/active_harness/http/retry_policy.rb
|
|
49
49
|
- lib/active_harness/http/streaming_client.rb
|
|
@@ -57,6 +57,8 @@ files:
|
|
|
57
57
|
- lib/active_harness/pipeline/hooks.rb
|
|
58
58
|
- lib/active_harness/pipeline/step.rb
|
|
59
59
|
- lib/active_harness/pricing.rb
|
|
60
|
+
- lib/active_harness/pricing/models_dev.rb
|
|
61
|
+
- lib/active_harness/pricing/openrouter.rb
|
|
60
62
|
- lib/active_harness/providers/PROVIDER_CONTRACT.md
|
|
61
63
|
- lib/active_harness/providers/anthropic.rb
|
|
62
64
|
- lib/active_harness/providers/azure.rb
|
|
@@ -67,6 +69,8 @@ files:
|
|
|
67
69
|
- lib/active_harness/providers/gemini.rb
|
|
68
70
|
- lib/active_harness/providers/gpustack.rb
|
|
69
71
|
- lib/active_harness/providers/groq.rb
|
|
72
|
+
- lib/active_harness/providers/images/openai.rb
|
|
73
|
+
- lib/active_harness/providers/images/openrouter.rb
|
|
70
74
|
- lib/active_harness/providers/mistral.rb
|
|
71
75
|
- lib/active_harness/providers/ollama.rb
|
|
72
76
|
- lib/active_harness/providers/openai.rb
|