| 1 | //! Factual model reference database (#3205, #2300). |
| 2 | //! |
| 3 | //! A browsable, read-only projection of the compiled catalog into per-offering |
| 4 | //! "fact cards": the model id as-is, the serving provider and its kind, the |
| 5 | //! context window, the price, and the modality (text vs multimodal). It exists |
| 6 | //! to answer "what are this model's stated attributes?", nothing more. |
| 7 | //! |
| 8 | //! This layer is **labels only**. It performs no selection, routing, tiering, |
| 9 | //! or ranking — it never decides which model to use, and it carries no |
| 10 | //! `strong`/`balanced`/`fast` or role concept. It is a superset-free view over |
| 11 | //! [`crate::catalog::CatalogOffering`] rows. |
| 12 | //! |
| 13 | //! Honesty rule (shared with #2608 / #3085): an attribute the catalog layer did |
| 14 | //! not state is reported as **unknown**, never guessed. A local/custom endpoint |
| 15 | //! with no catalog facts yields `Unknown` modality, `None` context window, and |
| 16 | //! an unknown price — its model id is still preserved verbatim. Nothing here is |
| 17 | //! inferred from a model-id prefix. |
| 18 | |
| 19 | use std::collections::{BTreeMap, BTreeSet}; |
| 20 | |
| 21 | use serde::{Deserialize, Serialize}; |
| 22 | |
| 23 | use crate::ProviderKind; |
| 24 | use crate::catalog::{CatalogOffering, CatalogSnapshot, CatalogSource, bundled_catalog_offerings}; |
| 25 | use crate::models_dev::ModelsDevModalities; |
| 26 | use crate::pricing::{Currency, OfferingPricing}; |
| 27 | |
| 28 | /// Coarse, factual input/output modality label for a model. |
| 29 | /// |
| 30 | /// `text` vs `multimodal` is derived from the union of stated input/output |
| 31 | /// modalities. Absent modality metadata is [`Modality::Unknown`], distinct from |
| 32 | /// a stated text-only model — "we were not told" is not "text only". |
| 33 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] |
| 34 | #[serde(rename_all = "snake_case")] |
| 35 | pub enum Modality { |
| 36 | /// Every stated modality is text. |
| 37 | Text, |
| 38 | /// At least one stated modality is non-text (image/audio/video/…). |
| 39 | Multimodal, |
| 40 | /// No modality metadata was stated for this row. |
| 41 | #[default] |
| 42 | Unknown, |
| 43 | } |
| 44 | |
| 45 | impl Modality { |
| 46 | /// Classify the modality from a Models.dev-shaped modality block. |
| 47 | /// |
| 48 | /// Returns [`Modality::Unknown`] for absent metadata or an empty list, |
| 49 | /// [`Modality::Multimodal`] when any stated input/output modality is not |
| 50 | /// `text`, and [`Modality::Text`] when the only stated modalities are text. |
| 51 | #[must_use] |
| 52 | pub fn from_modalities(modalities: Option<&ModelsDevModalities>) -> Self { |
| 53 | let Some(modalities) = modalities else { |
| 54 | return Self::Unknown; |
| 55 | }; |
| 56 | let mut saw_any = false; |
| 57 | for modality in modalities.input.iter().chain(modalities.output.iter()) { |
| 58 | let trimmed = modality.trim(); |
| 59 | if trimmed.is_empty() { |
| 60 | continue; |
| 61 | } |
| 62 | saw_any = true; |
| 63 | if !trimmed.eq_ignore_ascii_case("text") { |
| 64 | return Self::Multimodal; |
| 65 | } |
| 66 | } |
| 67 | if saw_any { Self::Text } else { Self::Unknown } |
| 68 | } |
| 69 | |
| 70 | /// Stable lowercase label. |
| 71 | #[must_use] |
| 72 | pub fn as_str(self) -> &'static str { |
| 73 | match self { |
| 74 | Self::Text => "text", |
| 75 | Self::Multimodal => "multimodal", |
| 76 | Self::Unknown => "unknown", |
| 77 | } |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | /// A factual reference card for one provider offering. |
| 82 | /// |
| 83 | /// Every field is either a stated fact or an explicit unknown. This is a |
| 84 | /// labels-only projection: it carries no routing, tier, or selection concept. |
| 85 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] |
| 86 | pub struct ModelReferenceCard { |
| 87 | /// Provider id serving this offering, exactly as the catalog row states it. |
| 88 | pub provider: String, |
| 89 | /// Resolved built-in provider kind, when the provider id maps to one. |
| 90 | /// |
| 91 | /// `None` for an unrecognized / user-named custom provider — an unknown |
| 92 | /// kind, not a guess. |
| 93 | #[serde(default, skip_serializing_if = "Option::is_none")] |
| 94 | pub provider_kind: Option<ProviderKind>, |
| 95 | /// The provider wire model id, verbatim. Never normalized or prefixed. |
| 96 | pub model_id: String, |
| 97 | /// Canonical model identity, only when the row carried an explicit join. |
| 98 | #[serde(default, skip_serializing_if = "Option::is_none")] |
| 99 | pub canonical_model: Option<String>, |
| 100 | /// Model family / series, when stated. |
| 101 | #[serde(default, skip_serializing_if = "Option::is_none")] |
| 102 | pub family: Option<String>, |
| 103 | /// Context-window tokens, when stated. |
| 104 | #[serde(default, skip_serializing_if = "Option::is_none")] |
| 105 | pub context_window: Option<u64>, |
| 106 | /// Max-output tokens, when stated. |
| 107 | #[serde(default, skip_serializing_if = "Option::is_none")] |
| 108 | pub max_output: Option<u64>, |
| 109 | /// Text vs multimodal, or unknown. |
| 110 | pub modality: Modality, |
| 111 | /// Per-token pricing facts, when priced. `None` is unknown, never free. |
| 112 | #[serde(default, skip_serializing_if = "Option::is_none")] |
| 113 | pub pricing: Option<OfferingPricing>, |
| 114 | /// Provenance of the underlying catalog row (bundled / live / override). |
| 115 | pub source: CatalogSource, |
| 116 | } |
| 117 | |
| 118 | impl ModelReferenceCard { |
| 119 | /// Project a catalog offering into its factual reference card. |
| 120 | #[must_use] |
| 121 | pub fn from_offering(offering: &CatalogOffering) -> Self { |
| 122 | Self { |
| 123 | provider: offering.provider.clone(), |
| 124 | provider_kind: ProviderKind::parse(&offering.provider), |
| 125 | model_id: offering.wire_model_id.clone(), |
| 126 | canonical_model: offering.canonical_model.clone(), |
| 127 | family: offering.family.clone(), |
| 128 | context_window: offering.limit.as_ref().and_then(|limit| limit.context), |
| 129 | max_output: offering.limit.as_ref().and_then(|limit| limit.output), |
| 130 | modality: Modality::from_modalities(offering.modalities.as_ref()), |
| 131 | pricing: OfferingPricing::from_catalog_offering(offering), |
| 132 | source: offering.source.clone(), |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | /// Label for the resolved provider kind, or `"unknown"`. |
| 137 | #[must_use] |
| 138 | pub fn provider_kind_label(&self) -> &'static str { |
| 139 | self.provider_kind.map_or("unknown", ProviderKind::as_str) |
| 140 | } |
| 141 | |
| 142 | /// Human context-window label such as `"1M"`, `"131K"`, `"512"`, or |
| 143 | /// `"unknown"`. The exact token count remains on [`Self::context_window`]. |
| 144 | #[must_use] |
| 145 | pub fn context_window_label(&self) -> String { |
| 146 | humanize_tokens(self.context_window) |
| 147 | } |
| 148 | |
| 149 | /// Human max-output label, same shape as [`Self::context_window_label`]. |
| 150 | #[must_use] |
| 151 | pub fn max_output_label(&self) -> String { |
| 152 | humanize_tokens(self.max_output) |
| 153 | } |
| 154 | |
| 155 | /// Short factual price label, e.g. `"$0.30 / $1.20 per Mtok"`, or |
| 156 | /// `"unknown"` when no per-token input/output rate is sourced. |
| 157 | /// |
| 158 | /// A `?` in one slot means that single rate is unknown while the other is |
| 159 | /// stated; a fully unknown price collapses to `"unknown"` rather than a |
| 160 | /// fabricated zero. |
| 161 | #[must_use] |
| 162 | pub fn price_label(&self) -> String { |
| 163 | let Some(pricing) = self.pricing.as_ref() else { |
| 164 | return "unknown".to_string(); |
| 165 | }; |
| 166 | if pricing.input_per_million.is_none() && pricing.output_per_million.is_none() { |
| 167 | return "unknown".to_string(); |
| 168 | } |
| 169 | let symbol = currency_symbol(&pricing.currency); |
| 170 | let render = |value: Option<f64>| match value { |
| 171 | Some(rate) => format!("{symbol}{rate:.2}"), |
| 172 | None => "?".to_string(), |
| 173 | }; |
| 174 | let suffix = currency_suffix(&pricing.currency); |
| 175 | format!( |
| 176 | "{} / {} per Mtok{suffix}", |
| 177 | render(pricing.input_per_million), |
| 178 | render(pricing.output_per_million), |
| 179 | ) |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | /// A browsable, read-only factual reference database of model offerings. |
| 184 | /// |
| 185 | /// Cards are sorted by `(provider, model id)` and de-duplicated on that |
| 186 | /// identity, so the database is deterministic regardless of input order. |
| 187 | #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] |
| 188 | pub struct ModelReferenceDatabase { |
| 189 | cards: Vec<ModelReferenceCard>, |
| 190 | } |
| 191 | |
| 192 | impl ModelReferenceDatabase { |
| 193 | /// Build from raw catalog offerings. |
| 194 | /// |
| 195 | /// Rows are keyed by `(provider, model id)`; a later row with the same |
| 196 | /// identity replaces an earlier one, matching catalog merge semantics. |
| 197 | #[must_use] |
| 198 | pub fn from_offerings(offerings: &[CatalogOffering]) -> Self { |
| 199 | let mut by_identity: BTreeMap<(String, String), ModelReferenceCard> = BTreeMap::new(); |
| 200 | for offering in offerings { |
| 201 | let card = ModelReferenceCard::from_offering(offering); |
| 202 | by_identity.insert((card.provider.clone(), card.model_id.clone()), card); |
| 203 | } |
| 204 | Self { |
| 205 | cards: by_identity.into_values().collect(), |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | /// Build from a compiled catalog snapshot (bundled < live < overrides). |
| 210 | #[must_use] |
| 211 | pub fn from_snapshot(snapshot: &CatalogSnapshot) -> Self { |
| 212 | Self::from_offerings(&snapshot.offerings) |
| 213 | } |
| 214 | |
| 215 | /// Build from CodeWhale's offline/stale bundled catalog snapshot (#4188). |
| 216 | /// |
| 217 | /// Prefer a live/compiled [`CatalogSnapshot`] when available. The bundled |
| 218 | /// set needs no credentials or network and remains the offline fallback |
| 219 | /// every install carries. |
| 220 | #[must_use] |
| 221 | pub fn bundled() -> Self { |
| 222 | Self::from_offerings(&bundled_catalog_offerings()) |
| 223 | } |
| 224 | |
| 225 | /// All cards, in stable `(provider, model id)` order. |
| 226 | #[must_use] |
| 227 | pub fn cards(&self) -> &[ModelReferenceCard] { |
| 228 | &self.cards |
| 229 | } |
| 230 | |
| 231 | /// Number of cards. |
| 232 | #[must_use] |
| 233 | pub fn len(&self) -> usize { |
| 234 | self.cards.len() |
| 235 | } |
| 236 | |
| 237 | /// Whether the database is empty. |
| 238 | #[must_use] |
| 239 | pub fn is_empty(&self) -> bool { |
| 240 | self.cards.is_empty() |
| 241 | } |
| 242 | |
| 243 | /// Distinct provider ids present, sorted. |
| 244 | #[must_use] |
| 245 | pub fn providers(&self) -> Vec<&str> { |
| 246 | self.cards |
| 247 | .iter() |
| 248 | .map(|card| card.provider.as_str()) |
| 249 | .collect::<BTreeSet<_>>() |
| 250 | .into_iter() |
| 251 | .collect() |
| 252 | } |
| 253 | |
| 254 | /// All cards served by one provider id. |
| 255 | #[must_use] |
| 256 | pub fn for_provider(&self, provider: &str) -> Vec<&ModelReferenceCard> { |
| 257 | self.cards |
| 258 | .iter() |
| 259 | .filter(|card| card.provider == provider) |
| 260 | .collect() |
| 261 | } |
| 262 | |
| 263 | /// Find a card by `(provider, model id)`. |
| 264 | #[must_use] |
| 265 | pub fn find(&self, provider: &str, model_id: &str) -> Option<&ModelReferenceCard> { |
| 266 | self.cards |
| 267 | .iter() |
| 268 | .find(|card| card.provider == provider && card.model_id == model_id) |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | /// Round a token count to a short human label (`"1M"`, `"203K"`, `"512"`), or |
| 273 | /// `"unknown"` for an absent count. Used for display only; callers needing the |
| 274 | /// exact value read the `Option<u64>` field directly. |
| 275 | fn humanize_tokens(tokens: Option<u64>) -> String { |
| 276 | let Some(tokens) = tokens else { |
| 277 | return "unknown".to_string(); |
| 278 | }; |
| 279 | if tokens >= 1_000_000 { |
| 280 | let millions = tokens as f64 / 1_000_000.0; |
| 281 | let rendered = format!("{millions:.2}"); |
| 282 | let trimmed = rendered.trim_end_matches('0').trim_end_matches('.'); |
| 283 | format!("{trimmed}M") |
| 284 | } else if tokens >= 1_000 { |
| 285 | format!("{}K", (tokens as f64 / 1_000.0).round() as u64) |
| 286 | } else { |
| 287 | tokens.to_string() |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | fn currency_symbol(currency: &Currency) -> &'static str { |
| 292 | match currency { |
| 293 | Currency::Usd => "$", |
| 294 | Currency::Cny => "¥", |
| 295 | Currency::Other(_) => "", |
| 296 | } |
| 297 | } |
| 298 | |
| 299 | fn currency_suffix(currency: &Currency) -> String { |
| 300 | match currency { |
| 301 | Currency::Usd | Currency::Cny => String::new(), |
| 302 | Currency::Other(code) => format!(" {code}"), |
| 303 | } |
| 304 | } |
| 305 | |
| 306 | #[cfg(test)] |
| 307 | mod tests { |
| 308 | use super::*; |
| 309 | use crate::models_dev::{ModelsDevCost, ModelsDevLimit}; |
| 310 | |
| 311 | fn offering(provider: &str, wire: &str) -> CatalogOffering { |
| 312 | CatalogOffering { |
| 313 | provider: provider.to_string(), |
| 314 | wire_model_id: wire.to_string(), |
| 315 | endpoint_key: "chat".to_string(), |
| 316 | source: CatalogSource::Bundled, |
| 317 | ..Default::default() |
| 318 | } |
| 319 | } |
| 320 | |
| 321 | #[test] |
| 322 | fn modality_text_multimodal_and_unknown() { |
| 323 | assert_eq!(Modality::from_modalities(None), Modality::Unknown); |
| 324 | assert_eq!( |
| 325 | Modality::from_modalities(Some(&ModelsDevModalities::default())), |
| 326 | Modality::Unknown, |
| 327 | "an empty modality block is unknown, not text-only" |
| 328 | ); |
| 329 | assert_eq!( |
| 330 | Modality::from_modalities(Some(&ModelsDevModalities { |
| 331 | input: vec!["text".to_string()], |
| 332 | output: vec!["text".to_string()], |
| 333 | })), |
| 334 | Modality::Text |
| 335 | ); |
| 336 | assert_eq!( |
| 337 | Modality::from_modalities(Some(&ModelsDevModalities { |
| 338 | input: vec!["text".to_string(), "image".to_string()], |
| 339 | output: vec!["text".to_string()], |
| 340 | })), |
| 341 | Modality::Multimodal |
| 342 | ); |
| 343 | // Case-insensitive and tolerant of an output-only non-text modality. |
| 344 | assert_eq!( |
| 345 | Modality::from_modalities(Some(&ModelsDevModalities { |
| 346 | input: vec!["TEXT".to_string()], |
| 347 | output: vec!["Audio".to_string()], |
| 348 | })), |
| 349 | Modality::Multimodal |
| 350 | ); |
| 351 | } |
| 352 | |
| 353 | #[test] |
| 354 | fn card_projects_stated_facts() { |
| 355 | let row = CatalogOffering { |
| 356 | family: Some("deepseek".to_string()), |
| 357 | limit: Some(ModelsDevLimit { |
| 358 | context: Some(1_000_000), |
| 359 | input: None, |
| 360 | output: Some(384_000), |
| 361 | }), |
| 362 | cost: Some(ModelsDevCost { |
| 363 | input: Some(0.3), |
| 364 | output: Some(1.2), |
| 365 | cache_read: Some(0.06), |
| 366 | cache_write: None, |
| 367 | }), |
| 368 | modalities: Some(ModelsDevModalities { |
| 369 | input: vec!["text".to_string()], |
| 370 | output: vec!["text".to_string()], |
| 371 | }), |
| 372 | ..offering("deepseek", "deepseek-v4-pro") |
| 373 | }; |
| 374 | let card = ModelReferenceCard::from_offering(&row); |
| 375 | |
| 376 | assert_eq!(card.provider, "deepseek"); |
| 377 | assert_eq!(card.provider_kind, Some(ProviderKind::Deepseek)); |
| 378 | assert_eq!(card.provider_kind_label(), "deepseek"); |
| 379 | assert_eq!(card.model_id, "deepseek-v4-pro"); |
| 380 | assert_eq!(card.family.as_deref(), Some("deepseek")); |
| 381 | assert_eq!(card.context_window, Some(1_000_000)); |
| 382 | assert_eq!(card.context_window_label(), "1M"); |
| 383 | assert_eq!(card.max_output, Some(384_000)); |
| 384 | assert_eq!(card.max_output_label(), "384K"); |
| 385 | assert_eq!(card.modality, Modality::Text); |
| 386 | assert_eq!(card.price_label(), "$0.30 / $1.20 per Mtok"); |
| 387 | } |
| 388 | |
| 389 | #[test] |
| 390 | fn custom_local_row_is_all_unknown_but_keeps_model_id_verbatim() { |
| 391 | // A user-named custom endpoint with no catalog facts: provider kind, |
| 392 | // context window, modality, and price are all unknown — never guessed — |
| 393 | // and the model id is preserved exactly. |
| 394 | let row = CatalogOffering { |
| 395 | source: CatalogSource::UserOverride, |
| 396 | ..offering("my-local-llm", "Vendor/Custom-Model_v1") |
| 397 | }; |
| 398 | let card = ModelReferenceCard::from_offering(&row); |
| 399 | |
| 400 | assert_eq!(card.provider_kind, None); |
| 401 | assert_eq!(card.provider_kind_label(), "unknown"); |
| 402 | assert_eq!(card.model_id, "Vendor/Custom-Model_v1"); |
| 403 | assert_eq!(card.context_window, None); |
| 404 | assert_eq!(card.context_window_label(), "unknown"); |
| 405 | assert_eq!(card.max_output_label(), "unknown"); |
| 406 | assert_eq!(card.modality, Modality::Unknown); |
| 407 | assert_eq!(card.price_label(), "unknown"); |
| 408 | } |
| 409 | |
| 410 | #[test] |
| 411 | fn unpriced_and_cache_only_rows_report_unknown_price_never_zero() { |
| 412 | // No cost block at all. |
| 413 | let unpriced = ModelReferenceCard::from_offering(&offering("deepseek", "deepseek-v4-pro")); |
| 414 | assert_eq!(unpriced.price_label(), "unknown"); |
| 415 | assert!(unpriced.pricing.is_none()); |
| 416 | |
| 417 | // A cost object priced only on cache classes is still unknown for the |
| 418 | // headline input/output rate label. |
| 419 | let cache_only = CatalogOffering { |
| 420 | cost: Some(ModelsDevCost { |
| 421 | input: None, |
| 422 | output: None, |
| 423 | cache_read: Some(0.05), |
| 424 | cache_write: None, |
| 425 | }), |
| 426 | ..offering("acme", "house-model") |
| 427 | }; |
| 428 | assert_eq!( |
| 429 | ModelReferenceCard::from_offering(&cache_only).price_label(), |
| 430 | "unknown" |
| 431 | ); |
| 432 | } |
| 433 | |
| 434 | #[test] |
| 435 | fn partial_price_renders_known_rate_and_marks_the_other_unknown() { |
| 436 | let row = CatalogOffering { |
| 437 | cost: Some(ModelsDevCost { |
| 438 | input: Some(5.0), |
| 439 | output: None, |
| 440 | cache_read: None, |
| 441 | cache_write: None, |
| 442 | }), |
| 443 | ..offering("openai", "gpt-5.5") |
| 444 | }; |
| 445 | assert_eq!( |
| 446 | ModelReferenceCard::from_offering(&row).price_label(), |
| 447 | "$5.00 / ? per Mtok" |
| 448 | ); |
| 449 | } |
| 450 | |
| 451 | #[test] |
| 452 | fn database_is_sorted_deduped_and_queryable() { |
| 453 | let rows = vec![ |
| 454 | CatalogOffering { |
| 455 | limit: Some(ModelsDevLimit { |
| 456 | context: Some(1), |
| 457 | input: None, |
| 458 | output: None, |
| 459 | }), |
| 460 | ..offering("zai", "GLM-5.2") |
| 461 | }, |
| 462 | offering("deepseek", "deepseek-v4-pro"), |
| 463 | // Duplicate identity with a higher context wins (last-write). |
| 464 | CatalogOffering { |
| 465 | limit: Some(ModelsDevLimit { |
| 466 | context: Some(1_000_000), |
| 467 | input: None, |
| 468 | output: None, |
| 469 | }), |
| 470 | ..offering("zai", "GLM-5.2") |
| 471 | }, |
| 472 | ]; |
| 473 | let db = ModelReferenceDatabase::from_offerings(&rows); |
| 474 | |
| 475 | assert_eq!(db.len(), 2, "duplicate (provider, model) collapses to one"); |
| 476 | // Sorted by (provider, model id): deepseek before zai. |
| 477 | assert_eq!(db.cards()[0].provider, "deepseek"); |
| 478 | assert_eq!(db.cards()[1].provider, "zai"); |
| 479 | assert_eq!(db.providers(), vec!["deepseek", "zai"]); |
| 480 | assert_eq!(db.for_provider("zai").len(), 1); |
| 481 | assert_eq!( |
| 482 | db.find("zai", "GLM-5.2") |
| 483 | .and_then(|card| card.context_window), |
| 484 | Some(1_000_000), |
| 485 | "last-write-wins kept the richer row" |
| 486 | ); |
| 487 | assert!(db.find("zai", "missing").is_none()); |
| 488 | } |
| 489 | |
| 490 | #[test] |
| 491 | fn bundled_database_is_nonempty_and_honest() { |
| 492 | let db = ModelReferenceDatabase::bundled(); |
| 493 | assert!(!db.is_empty()); |
| 494 | assert!( |
| 495 | db.len() >= 20, |
| 496 | "bundled offline snapshot should carry seed offerings, got {}", |
| 497 | db.len() |
| 498 | ); |
| 499 | |
| 500 | // Every card preserves a non-empty model id and resolves a known kind |
| 501 | // for the bundled (first-class) providers. |
| 502 | for card in db.cards() { |
| 503 | assert!(!card.model_id.is_empty()); |
| 504 | assert!( |
| 505 | card.provider_kind.is_some(), |
| 506 | "bundled provider {} should map to a known kind", |
| 507 | card.provider |
| 508 | ); |
| 509 | } |
| 510 | |
| 511 | // A DeepSeek-native row: context window known, price honestly unknown |
| 512 | // (the bundled snapshot omits DeepSeek-native per-token pricing). |
| 513 | let deepseek = db |
| 514 | .find("deepseek", "deepseek-v4-pro") |
| 515 | .expect("bundled deepseek row"); |
| 516 | assert_eq!(deepseek.context_window, Some(1_000_000)); |
| 517 | assert_eq!(deepseek.modality, Modality::Text); |
| 518 | assert_eq!(deepseek.price_label(), "unknown"); |
| 519 | |
| 520 | // A priced row surfaces its stated per-token rate. |
| 521 | let minimax = db |
| 522 | .find("minimax", "MiniMax-M2.7") |
| 523 | .expect("bundled minimax row"); |
| 524 | assert_eq!(minimax.price_label(), "$0.30 / $1.20 per Mtok"); |
| 525 | |
| 526 | // MiniMax-M3 ships without verified per-token pricing, so its price |
| 527 | // stays honestly unknown rather than inheriting a sibling's rate. |
| 528 | let minimax_m3 = db |
| 529 | .find("minimax", "MiniMax-M3") |
| 530 | .expect("bundled minimax m3 row"); |
| 531 | assert_eq!(minimax_m3.price_label(), "unknown"); |
| 532 | } |
| 533 | |
| 534 | #[test] |
| 535 | fn humanize_tokens_shapes() { |
| 536 | assert_eq!(humanize_tokens(None), "unknown"); |
| 537 | assert_eq!(humanize_tokens(Some(512)), "512"); |
| 538 | assert_eq!(humanize_tokens(Some(131_072)), "131K"); |
| 539 | assert_eq!(humanize_tokens(Some(1_000_000)), "1M"); |
| 540 | assert_eq!(humanize_tokens(Some(1_050_000)), "1.05M"); |
| 541 | } |
| 542 | } |
| 543 |