Module: ActiveHarness::Pricing::OpenRouter

Defined in:
lib/active_harness/pricing/openrouter.rb

Overview

Fetches complete pricing for all OpenRouter models across all modalities.

OpenRouter exposes models via several endpoints:

GET /api/v1/models                          → 337 text models (base)
GET /api/v1/models?output_modalities=image  → 32 image-gen models (25 extra)
GET /api/v1/models?output_modalities=embeddings    → 26 models (all extra)
GET /api/v1/models?output_modalities=speech        →  9 models (all extra)
GET /api/v1/models?output_modalities=transcription → 10 models (all extra)
GET /api/v1/models?output_modalities=video         → 14 models (all zero pricing)
GET /api/v1/models?output_modalities=rerank        →  4 models (all zero pricing)

For image-output models, /api/v1/models/id/endpoints is also fetched to get the accurate ‘image_output` per-token rate.

All models are merged by id; pricing fields are populated per-modality:

text_input / text_output — text tokens
image_input              — image tokens accepted as input (vision)
image_output             — image generation tokens (from /endpoints)
audio_input              — audio tokens as input
audio_output             — audio tokens as output (TTS)
cache_read / cache_write — cache tokens
web_search               — per web-search request

Usage:

Pricing::OpenRouter.find("openai/gpt-5-image-mini")  # → ModelPrice or nil
Pricing::OpenRouter.all                              # → Array<ModelPrice>
Pricing::OpenRouter.update                           # force refresh

Constant Summary collapse

API_BASE =
"https://openrouter.ai/api/v1/models"
MEMORY_TTL =

3 days

3 * 86_400
EXTRA_MODALITIES =

Modalities that have models outside the base text-337 set.

%w[image embeddings speech transcription video rerank].freeze

Class Method Summary collapse

Class Method Details

.allObject



49
50
51
52
# File 'lib/active_harness/pricing/openrouter.rb', line 49

def all
  ensure_fresh_registry
  registry.filter_map { |raw| build_price(raw) }
end

.cache_fileObject



75
76
77
# File 'lib/active_harness/pricing/openrouter.rb', line 75

def cache_file
  File.join(project_root, "tmp", "active_harness", "openrouter_pricing.json")
end

.find(model_id) ⇒ Object



43
44
45
46
47
# File 'lib/active_harness/pricing/openrouter.rb', line 43

def find(model_id)
  ensure_fresh_registry
  raw = registry.find { |m| m[:id] == model_id.to_s }
  raw ? build_price(raw) : nil
end

.preload!Object



54
55
56
57
58
59
60
61
# File 'lib/active_harness/pricing/openrouter.rb', line 54

def preload!
  update
rescue StandardError
  nil
ensure
  @registry  = load_registry
  @loaded_at = @registry.empty? ? nil : Time.now
end

.reload!Object



70
71
72
73
# File 'lib/active_harness/pricing/openrouter.rb', line 70

def reload!
  @registry  = nil
  @loaded_at = nil
end

.updateObject



63
64
65
66
67
68
# File 'lib/active_harness/pricing/openrouter.rb', line 63

def update
  entries = collect_all_models
  FileUtils.mkdir_p(File.dirname(cache_file))
  File.write(cache_file, JSON.generate(entries))
  entries.size
end