package main // pricing.go centralises model pricing as a per-model table (the LiteLLM pattern) // instead of three hardcoded Grok fields. The spend ledger prices each call by the // model it actually used, so when a second model (Gemini) starts answering some // routes, its cost books correctly against the same global ceiling. // ModelPrice is the per-1M-token USD price for one model, applied to the API's // returned usage so the wallet ceiling tracks real cost even as prices change. type ModelPrice struct { InputPerM float64 // non-cached prompt tokens CachedPerM float64 // prompt tokens served from the provider cache (cheaper) OutputPerM float64 // completion tokens } // CostBreakdown is the per-component USD cost of answering one request. A plain // grok_direct call has only Token; a cascade adds Router (the cheap classifier), // Grounding (Gemini Google-search) and/or WebTool (Grok web search) on top. Settle // books each column separately so the ledger and request_log can attribute spend, // and so a half-finished cascade can book only what it actually spent (§8.1). type CostBreakdown struct { Token float64 Grounding float64 // Gemini grounded-prompt TOKEN cost WebTool float64 Router float64 // GroundingFee is the per-grounded-prompt FEE (the $35/1k overage on a paid Gemini // tier, GEMINI_GROUNDING_PER_PROMPT_USD) — kept separate from Grounding (the token // cost) for clean analytics. Booked the moment the grounded prompt is admitted, even // on the error return (§7 SG1). Settle folds it into the grounding_usd spend column, // so the $10 ceiling finally sees it without a spend-table migration. GroundingFee float64 } // Total is the grand total across all components (the number the wallet ceiling and // request_log.total_usd care about). Computed, never stored, so it can't drift. func (c CostBreakdown) Total() float64 { return c.Token + c.Grounding + c.WebTool + c.Router + c.GroundingFee } // priceFor returns the configured price for a model. An unknown model falls back to // the default (final-voice) model's price rather than $0 — a $0 price would silently // blind the global ceiling to that call, the one failure mode we never want. func (c *Config) priceFor(model string) ModelPrice { if p, ok := c.Prices[model]; ok { return p } return c.Prices[c.XAIModel] }