OptionalpriceUnitType: MJAIModelPriceUnitTypeEntityProtected Optional ReadonlyPriceThe MJ: AI Model Price Unit Types row this driver was resolved for, when the caller supplied
it — AIEngineBase.GetPriceCalculator always does.
Optional so any subclass outside this repo — which predates the parameter entirely — keeps working: extra constructor arguments are inert in JS. When present it is authoritative for UnitsPerBillingUnit, which is what makes that column mean something rather than decorate a form.
ProtectedDeclaredThe scale this driver compiles in, used ONLY when no catalog row was supplied.
Defaults to 1 — "the quantity IS the number of billed units" — which is both the correct answer for a per-image rate and a safe default for any subclass outside this repo that predates this property.
The measure named by the row's usage type. Falls back to Tokens only when this driver was
constructed with no row at all, which GetPriceCalculator never does — a caller that
instantiates it directly gets the same default every other driver has.
How many quantities in this driver's UnitKind make up ONE billed unit — 1,000,000 for a per-million-tokens rate, 3,600 for a per-hour rate, 1 for a per-image rate.
The catalog row wins when one was supplied, and GetPriceCalculator always supplies it.
The compiled-in literal is the fallback for a driver instantiated directly (tests, and any
consumer that wants a scale without loading the engine).
Reading the column rather than the literal is what makes UnitsPerBillingUnit mean something.
With the literal authoritative, the column was decoration: an admin could edit Per Hour to
7200 through the generated form, save successfully, and change nothing about how anything
priced — the mirror image of B60, where the data was present and the code ignored it. A
non-positive or non-finite column value falls through to the literal rather than poisoning a
cost with Infinity/NaN; the database's CK_AIModelPriceUnitType_UnitsPerBillingUnit CHECK (> 0) is the real guard, and reaching this fallback means something bypassed it.
Prices a run from its quantities, whatever measure those are in. This is the entry point callers should use: it is the only one that works uniformly across token and continuous-media pricing, so a caller need not branch on which kind of model it just ran.
The default delegates to CalculateNormalizedCostWithCache, so every existing driver — including subclasses outside this repo — gets it for free with unchanged behavior.
Callers are responsible for handing over quantities in this driver's UnitKind;
AIEngineBase.CalculateModelCost verifies that before calling.
Calculates normalized cost based on each sub-classes specific algorithm.
The two quantities are expressed in this driver's UnitKind: token counts for the token drivers, seconds of audio for the time drivers, a count of images for the per-image driver. The parameter names are historical — they predate any non-token unit type.
The active cost configuration
Input quantity, in this driver's unit kind
Output quantity, in this driver's unit kind
The calculated cost
Cache-aware cost calculation: prices the three input buckets (uncached/net-new, cache reads, cache writes) at their own per-unit rates, plus output. Cache reads/writes use CacheReadPricePerUnit / CacheWritePricePerUnit when recorded on the cost row; when those are NULL they fall back to InputPricePerUnit, which makes the result identical to the legacy single-bucket pricing. This is the entry point cost calculators should prefer.
The default implementation here preserves the legacy behavior (all input at the input rate) so any external BasePriceUnitType subclass that only overrides CalculateNormalizedCost keeps working unchanged. The built-in per-unit types below override it to apply per-bucket rates.
ProtectedInternalProtectedInternalPer-bucket cost math shared by the built-in unit types. Cache rates fall back to the input rate when not populated, so cost is unchanged until a model/vendor records a distinct cache rate. All buckets are normalized by the same divisor (e.g. 1,000,000 for per-1M-tokens).
Prices any LINEAR billing unit entirely from its own
MJ: AI Model Price Unit Typesrow.Why this exists — it closes B60's class, not just its instance
The three continuous-media unit types that shipped uncosted (
Per Image,Per Minute,Per Hour) were seeded as DATA by one person while the driver classes that price them were never written by another. The seam was silent for months. Every hardcoded driver below is a standing invitation to repeat that: seeding a row is not sufficient, so a row can always exist without the code that gives it meaning.With the measure (
UsageTypeID) and the divisor (UnitsPerBillingUnit) held as columns, a linear unit type needs no code at all — it names this driver and states its own arithmetic. "Per 1,000 Characters" becomes one seeded row rather than a row plus a class plus a registration plus a release.Why unregistered driver names still refuse
This is opt-in via
DriverClass = 'Linear'rather than a fallback for any unresolvable driver.DriverClassis NOT NULL, so every row names something, and an unrecognised name is ambiguous between "a new linear unit type" and "a genuinely non-linear driver whose code is missing". Pricing the second linearly would produce a confident wrong number, which is the one outcome this whole subsystem is built to avoid. So an unknown driver keeps refusing, and a row that WANTS data-driven pricing says so.DriverClasstherefore remains the escape hatch for genuinely non-linear pricing: tiered rates, per-image-by-resolution, or minimum-billing increments such as the Groq 10-second floor.