Module: Smith::Pricing

Defined in:
lib/smith/pricing.rb

Class Method Summary collapse

Class Method Details

.compute_cost(model:, input_tokens:, output_tokens:, provider: nil) ⇒ Object

Compute provider cost for a single agent call. Two pricing shapes are supported:

Flat (existing): the catalog entry is a Hash with
`:input_cost_per_token` / `:output_cost_per_token` keys. Used
for models with a single rate across all input sizes
(Gemini 2.5 Flash, Claude Opus 4.6/4.7).

Tiered (new): the catalog entry has a `:tiers` array of bracket
hashes, each with `:max_input_tokens` (nil = unbounded ceiling),
`:input_cost_per_token`, `:output_cost_per_token`. Tiers are
walked in order; the first whose `max_input_tokens` covers the
call's input_tokens picks the rate. Used for models with
long-context premium pricing (Gemini 2.5 Pro: $1.25/$10 below
200K input tokens, $2.50/$15 above).

Catalog keys are normalized once per assigned catalog object:

["provider", "model"] / [:provider, :model]  -> provider-qualified
"provider/model" (first slash splits)        -> provider-qualified
"model" / :model (no slash)                  -> legacy model-only

Lookup policy: a provider-qualified lookup reads only qualified entries and NEVER prices from a legacy model-only rate; unpriced usage stays visibly unpriced (nil cost). This method never raises, so accounting paths cannot mask a provider error with a pricing configuration error. The legacy-only-key policy is enforced at catalog admission time via validate_catalog!.



32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/smith/pricing.rb', line 32

def self.compute_cost(model:, input_tokens:, output_tokens:, provider: nil)
  catalog = Smith.config.pricing
  return nil unless catalog.is_a?(Hash)

  entry = lookup_entry(normalized_index(catalog), model, provider)
  return nil unless entry

  input_rate, output_rate = resolve_rates(entry, input_tokens)
  return nil unless input_rate

  (input_tokens * input_rate) + (output_tokens * output_rate)
end

.validate_catalog!(catalog = Smith.config.pricing) ⇒ Object

Admission-time catalog validation. Call when the pricing catalog is assigned (config.pricing = Smith::Pricing.validate_catalog!(catalog)) so misconfiguration fails at configuration time instead of inside an in-flight accounting path. Rejects legacy model-only keys, unrecognized key shapes, non-Hash entries, and keys that collide after normalization. Returns the catalog for assignment chaining.



57
58
59
60
61
62
63
64
65
66
67
# File 'lib/smith/pricing.rb', line 57

def self.validate_catalog!(catalog = Smith.config.pricing)
  return catalog if catalog.nil?
  raise Smith::PricingConfigurationError, "pricing catalog must be a Hash" unless catalog.is_a?(Hash)

  catalog.each_with_object({}) do |(key, entry), seen|
    provider, model = validated_key!(key)
    validate_entry!(key, entry)
    reject_collision!(seen, key, provider, model)
  end
  catalog
end