Skip to content

ModelPricing has no non-token unit, so imagen-4.0-generate-001 ships a per-image price in a per-1M-token field — a 20,000x under-report the new guard exempts #938

Description

@sroussey

What

providers/google-gemini/src/ai/common/Gemini_Pricing.ts:118:

"imagen-4.0-generate-001": { currency: "USD", input: 0.03, output: 0.03 },

0.03 is Imagen's per-image list price. Every field on ModelPricing is per 1M tokens. Executed at 2d36880:

getGeminiModelPricing("imagen-4.0-generate-001") = {"currency":"USD","input":0.03,"output":0.03}

cost a host computes for one image on a 50-token prompt:
  (50 / 1e6) * 0.03  =  $0.0000015
Imagen 4 list price:  $0.03-0.04 per image
under-report factor:  20,000x

At any realistic prompt length the reported cost of an Imagen call rounds to zero next to the real bill.

This is already known, and the guard that would catch it exempts it

packages/test/src/test/ai-provider/inferAdvertisesRegistered.test.ts:303-313, written this window while closing #909, says so:

Gemini's flash and pro image models genuinely bill by token — text tokens in, image tokens out. imagen-4.0-generate-001 is the doubtful one: $0.03 is Imagen's per-IMAGE list price sitting in a per-1M-token field, which is the unit confusion this assertion exists to surface. Left recorded rather than changed here, because correcting it means either a per-image field on ModelPricing or dropping a real rate, and neither is a test's call.

That is the right call for a test. It is also the correct diagnosis, and it names the fix. The consequence of leaving it is that assertPricingMatchesModality — the assertion built specifically to stop a fabricated unit — reports green over the one fabricated unit left in the tree, via namedByTable: ["imagen-4.0-generate-001", "gemini-3.1-flash-image"] (:348).

Note the exemption list mixes two different justifications under one key: gemini-3.1-flash-image and gemini-3-pro-image are exempt because they genuinely bill by token, which is correct and permanent; imagen-4.0-generate-001 is exempt because nobody has anywhere to put its number, which is a defect with a deadline.

Root cause

packages/ai/src/model/ModelPricing.ts:22-33, 76-81:

export interface ModelPricingBase {
  input?: number;
  output?: number;
  cached?: number;
  cacheWrite?: number | { cacheWrite5m?: number; cacheWrite1h?: number };
  cacheStoragePerHour?: number;
}
export interface ModelPricing extends ModelPricingBase {
  currency: string;
  batch?: ModelPricingBase;
  usageTiers?: ModelUsageTier[];
  timingTiers?: ModelTimingTier[];
}

Every field is per-1M-tokens. There is no perImage, no perRequest, no unit discriminator. Three vendors in this repo bill in a unit it cannot express:

  • Imagen / DALL-E / Grok image — per image
  • OpenRouter — a request fee and an image fee per route (see the companion issue)
  • OpenAI tts-1 / whisper-1 — per character and per minute

Proposed fix

  1. Add the missing unit to ModelPricingBase, e.g.

    /** Charged once per request regardless of tokens (OpenRouter routes, some search tools). */
    perRequest?: number;
    /** Charged per generated image. A card carrying this is not a token card. */
    perImage?: number;

    and teach CostEstimate to add them. Two fields, both optional, no migration.

  2. Then imagen-4.0-generate-001 becomes { currency: "USD", perImage: 0.03 } and quotesTokenRate() (pricingMatchesModality.ts:32-39) returns false for it, so it leaves namedByTable by the assertion's own rule — which is what assertPricingMatchesModality's stale-entry check at :82-96 is already written to demand.

  3. Replace namedByTable: readonly string[] with readonly { id: string; because: string }[] so an exemption records why. Today the list cannot distinguish "bills by token, correctly exempt forever" from "we have nowhere to put the real number".

If (1) is too large for one change, the interim honest answer is to drop the imagen-4.0-generate-001 row — unpriced is the documented correct answer for a model the table cannot price (ModelPricing.ts:105-112), and reporting $0.0000015 for a $0.03 call is worse than reporting nothing.

Found during the 2026-09-14 review of providers/. Verified against origin/main @ 2d36880. Companion to the OpenRouter per-image issue.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions