1use super::{Key, Tag};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum ProviderKind {
18 Ollama,
20 OpenRouter,
22 MimoTokenPlan,
25 KimiCode,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum Wire {
32 Anthropic,
34 OpenAi,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub struct Region {
42 pub id: &'static str,
44 pub base: &'static str,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub struct Published {
53 pub id: &'static str,
55 pub window: u64,
57 pub source: &'static str,
59}
60
61const MIMO_MODELS: &str = "https://mimo.mi.com/static/docs/quick-start/summary/model.md";
65
66const KIMI_MODELS: &str = "https://www.kimi.com/code/docs/en/third-party-tools/claude-code";
71
72const MIMO_PUBLISHED: [Published; 4] = [
73 Published { id: "mimo-v2.6-pro", window: 1_048_576, source: MIMO_MODELS },
74 Published { id: "mimo-v2.6-flash", window: 1_048_576, source: MIMO_MODELS },
75 Published { id: "mimo-v2.5-pro", window: 1_048_576, source: MIMO_MODELS },
76 Published { id: "mimo-v2.5", window: 1_048_576, source: MIMO_MODELS },
77];
78
79const KIMI_PUBLISHED: [Published; 4] = [
80 Published { id: "kimi-for-coding", window: 1_048_576, source: KIMI_MODELS },
81 Published { id: "k3", window: 262_144, source: KIMI_MODELS },
82 Published { id: "k3-256k", window: 262_144, source: KIMI_MODELS },
83 Published { id: "kimi-for-coding-highspeed", window: 262_144, source: KIMI_MODELS },
84];
85
86const MIMO_REGIONS: [Region; 3] = [
90 Region { id: "europe", base: "https://token-plan-ams.xiaomimimo.com" },
91 Region { id: "singapore", base: "https://token-plan-sgp.xiaomimimo.com" },
92 Region { id: "china", base: "https://token-plan-cn.xiaomimimo.com" },
93];
94
95const KIMI_REGIONS: [Region; 2] =
99 [Region { id: "china", base: "https://api.kimi.com" }, Region { id: "overseas", base: "https://api.kimi.ai" }];
100
101impl ProviderKind {
102 pub const ALL: [Self; 4] = [Self::Ollama, Self::OpenRouter, Self::MimoTokenPlan, Self::KimiCode];
104
105 #[must_use]
107 pub fn id(self) -> &'static str {
108 match self {
109 Self::Ollama => "ollama",
110 Self::OpenRouter => "openrouter",
111 Self::MimoTokenPlan => "mimo-token-plan",
112 Self::KimiCode => "kimi-code",
113 }
114 }
115
116 #[must_use]
118 pub fn parse(id: &str) -> Option<Self> {
119 Self::ALL.into_iter().find(|kind| kind.id() == id)
120 }
121
122 #[must_use]
126 pub fn suggested_base(self) -> &'static str {
127 match self {
128 Self::Ollama => "http://127.0.0.1:11434",
129 Self::OpenRouter => "https://openrouter.ai",
130 Self::MimoTokenPlan => MIMO_REGIONS[0].base,
131 Self::KimiCode => KIMI_REGIONS[0].base,
132 }
133 }
134
135 #[must_use]
139 pub fn suggested_tag(self) -> Option<&'static str> {
140 match self {
141 Self::Ollama | Self::OpenRouter => None,
142 Self::MimoTokenPlan => Some("mimo"),
143 Self::KimiCode => Some("kimi"),
144 }
145 }
146
147 #[must_use]
150 pub fn regions(self) -> &'static [Region] {
151 match self {
152 Self::Ollama | Self::OpenRouter => &[],
153 Self::MimoTokenPlan => &MIMO_REGIONS,
154 Self::KimiCode => &KIMI_REGIONS,
155 }
156 }
157
158 #[must_use]
161 pub fn needs_key(self) -> bool {
162 match self {
163 Self::Ollama => false,
164 Self::OpenRouter | Self::MimoTokenPlan | Self::KimiCode => true,
165 }
166 }
167
168 #[must_use]
170 pub fn wire(self) -> Wire {
171 Wire::Anthropic
172 }
173
174 #[must_use]
181 pub fn key_header(self) -> (&'static str, &'static str) {
182 match self {
183 Self::Ollama | Self::OpenRouter => ("Authorization", "Bearer "),
184 Self::MimoTokenPlan => ("api-key", ""),
185 Self::KimiCode => ("x-api-key", ""),
186 }
187 }
188
189 #[must_use]
201 pub fn api_root(self, wire: Wire) -> &'static str {
202 match (self, wire) {
203 (Self::Ollama, _) | (Self::MimoTokenPlan, Wire::OpenAi) => "",
204 (Self::OpenRouter, _) => "/api",
205 (Self::MimoTokenPlan, Wire::Anthropic) => "/anthropic",
206 (Self::KimiCode, _) => "/coding",
207 }
208 }
209
210 #[must_use]
217 pub fn api_address(self, base: &str, wire: Wire, path: &str) -> String {
218 let base = trim_base(base);
219 let mut endings: Vec<String> = Wire::ALL
220 .iter()
221 .map(|wire| self.api_root(*wire))
222 .flat_map(|root| [format!("{root}/v1"), root.to_owned()])
223 .filter(|ending| !ending.is_empty())
224 .collect();
225 endings.sort_by_key(|ending| std::cmp::Reverse(ending.len()));
228 let host = endings.iter().find_map(|ending| base.strip_suffix(ending.as_str())).unwrap_or(&base);
229 format!("{host}{}{path}", self.api_root(wire))
230 }
231
232 #[must_use]
235 pub fn published(self) -> &'static [Published] {
236 match self {
237 Self::Ollama | Self::OpenRouter => &[],
238 Self::MimoTokenPlan => &MIMO_PUBLISHED,
239 Self::KimiCode => &KIMI_PUBLISHED,
240 }
241 }
242
243 #[must_use]
251 pub fn claims_are_served(self) -> bool {
252 matches!(self, Self::MimoTokenPlan | Self::KimiCode)
253 }
254}
255
256impl Wire {
257 pub const ALL: [Self; 2] = [Self::Anthropic, Self::OpenAi];
259
260 #[must_use]
262 pub fn id(self) -> &'static str {
263 match self {
264 Self::Anthropic => "anthropic",
265 Self::OpenAi => "openai",
266 }
267 }
268
269 #[must_use]
271 pub fn parse(id: &str) -> Option<Self> {
272 Self::ALL.into_iter().find(|wire| wire.id() == id)
273 }
274
275 #[must_use]
277 pub fn messages_path(self) -> &'static str {
278 match self {
279 Self::Anthropic => "/v1/messages",
280 Self::OpenAi => "/v1/chat/completions",
281 }
282 }
283
284 #[must_use]
288 pub fn of_path(path: &str) -> Self {
289 let path = path.split('?').next().unwrap_or(path);
290 if path == Self::Anthropic.messages_path() { Self::Anthropic } else { Self::OpenAi }
291 }
292}
293
294#[derive(Debug, Clone, Copy, PartialEq, Eq)]
302pub enum Measured {
303 About(u64),
305 AtLeast(u64),
307}
308
309impl Measured {
310 #[must_use]
312 pub fn tokens(self) -> u64 {
313 match self {
314 Self::About(tokens) | Self::AtLeast(tokens) => tokens,
315 }
316 }
317
318 #[must_use]
320 pub fn id(self) -> &'static str {
321 match self {
322 Self::About(_) => "about",
323 Self::AtLeast(_) => "at-least",
324 }
325 }
326
327 #[must_use]
329 pub fn parse(id: &str, tokens: u64) -> Option<Self> {
330 match id {
331 "about" => Some(Self::About(tokens)),
332 "at-least" => Some(Self::AtLeast(tokens)),
333 _ => None,
334 }
335 }
336
337 #[must_use]
343 pub fn is_cramped(self) -> bool {
344 matches!(self, Self::About(tokens) if tokens < CRAMPED)
345 }
346}
347
348pub const CRAMPED: u64 = 16_384;
352
353#[derive(Debug, Clone, Copy, PartialEq, Eq)]
360pub enum Price {
361 Free,
363 Paid,
365}
366
367impl Price {
368 pub const ALL: [Self; 2] = [Self::Free, Self::Paid];
370
371 #[must_use]
373 pub fn id(self) -> &'static str {
374 match self {
375 Self::Free => "free",
376 Self::Paid => "paid",
377 }
378 }
379
380 #[must_use]
382 pub fn parse(id: &str) -> Option<Self> {
383 Self::ALL.into_iter().find(|price| price.id() == id)
384 }
385}
386
387#[derive(Debug, Clone, PartialEq, Eq)]
389pub struct Model {
390 pub id: String,
392 pub claimed: Option<u64>,
394 pub measured: Option<Measured>,
396 pub price: Option<Price>,
398}
399
400impl Model {
401 #[must_use]
403 pub fn new(id: impl Into<String>) -> Self {
404 Self { id: id.into(), claimed: None, measured: None, price: None }
405 }
406
407 #[must_use]
411 pub fn window(&self, kind: ProviderKind) -> Option<u64> {
412 self.measured.map(Measured::tokens).or_else(|| self.claimed.filter(|_| kind.claims_are_served()))
413 }
414
415 #[must_use]
418 pub fn published_at(&self, kind: ProviderKind) -> Option<&'static str> {
419 kind.published()
420 .iter()
421 .find(|published| published.id == self.id && Some(published.window) == self.claimed)
422 .map(|published| published.source)
423 }
424
425 #[must_use]
428 pub fn is_short_changed(&self) -> bool {
429 match (self.claimed, self.measured) {
430 (Some(claimed), Some(measured)) => measured.tokens() * 2 < claimed,
431 _ => false,
432 }
433 }
434}
435
436#[derive(Debug, Clone, PartialEq, Eq)]
442pub struct Lineup {
443 pub name: Tag,
445 pub models: Vec<String>,
447}
448
449impl Lineup {
450 #[must_use]
458 pub fn window(&self, entry: &ProviderEntry) -> Option<u64> {
459 self.models.iter().filter_map(|id| entry.model(id)?.window(entry.kind)).min()
460 }
461
462 #[must_use]
466 pub fn paid<'a>(&self, entry: &'a ProviderEntry) -> Vec<&'a str> {
467 self.models
468 .iter()
469 .filter_map(|id| entry.model(id))
470 .filter(|model| model.price == Some(Price::Paid))
471 .map(|model| model.id.as_str())
472 .collect()
473 }
474
475 #[must_use]
479 pub fn unknown_window(&self, entry: &ProviderEntry) -> Vec<&str> {
480 self.models
481 .iter()
482 .filter(|id| entry.model(id).and_then(|model| model.window(entry.kind)).is_none())
483 .map(String::as_str)
484 .collect()
485 }
486}
487
488#[derive(Debug, Clone, PartialEq, Eq)]
490pub struct ProviderEntry {
491 pub tag: Tag,
493 pub kind: ProviderKind,
495 pub base: String,
497 pub wire: Wire,
499 pub models: Vec<Model>,
501 pub key: Option<Key>,
503 pub lineups: Vec<Lineup>,
506}
507
508impl ProviderEntry {
509 #[must_use]
511 pub fn new(tag: Tag, kind: ProviderKind, base: &str) -> Self {
515 let models = kind
516 .published()
517 .iter()
518 .map(|published| Model {
519 id: published.id.to_owned(),
520 claimed: Some(published.window),
521 measured: None,
522 price: None,
523 })
524 .collect();
525 Self { tag, kind, base: trim_base(base), wire: kind.wire(), models, key: None, lineups: Vec::new() }
526 }
527
528 #[must_use]
531 pub fn address(&self, path: &str) -> String {
532 format!("{}{path}", self.base)
533 }
534
535 #[must_use]
538 pub fn api_address(&self, wire: Wire, path: &str) -> String {
539 self.kind.api_address(&self.base, wire, path)
540 }
541
542 #[must_use]
545 pub fn messages_address(&self) -> String {
546 self.api_address(self.wire, self.wire.messages_path())
547 }
548
549 #[must_use]
551 pub fn model(&self, id: &str) -> Option<&Model> {
552 self.models.iter().find(|model| model.id == id)
553 }
554
555 #[must_use]
558 pub fn lineup(&self, name: &str) -> Option<&Lineup> {
559 self.lineups.iter().find(|lineup| lineup.name.as_str() == name)
560 }
561}
562
563#[must_use]
566pub fn trim_base(base: &str) -> String {
567 base.trim().trim_end_matches('/').to_owned()
568}
569
570#[cfg(test)]
571mod tests {
572 use super::*;
573
574 fn tag(name: &str) -> Tag {
575 Tag::parse(name).expect("a tag")
576 }
577
578 #[test]
579 fn a_pasted_address_never_doubles_its_slash() {
580 let entry = ProviderEntry::new(tag("ev"), ProviderKind::Ollama, " http://192.168.122.1:11434/ ");
581 assert_eq!(entry.base, "http://192.168.122.1:11434");
582 assert_eq!(entry.messages_address(), "http://192.168.122.1:11434/v1/messages");
583 assert_eq!(entry.address("/api/tags"), "http://192.168.122.1:11434/api/tags");
584 }
585
586 #[test]
587 fn an_openai_shaped_provider_is_asked_at_the_other_path() {
588 let mut entry = ProviderEntry::new(tag("ev"), ProviderKind::Ollama, "http://h:1");
589 assert_eq!(entry.messages_address(), "http://h:1/v1/messages", "both kinds speak Anthropic by default");
590 entry.wire = Wire::OpenAi;
591 assert_eq!(entry.messages_address(), "http://h:1/v1/chat/completions");
592 }
593
594 #[test]
595 fn openrouter_is_asked_under_its_api_whichever_of_its_two_addresses_was_written() {
596 for base in [
599 "https://openrouter.ai",
600 "https://openrouter.ai/",
601 "https://openrouter.ai/api",
602 "https://openrouter.ai/api/",
603 ] {
604 let entry = ProviderEntry::new(tag("yol"), ProviderKind::OpenRouter, base);
605 assert_eq!(entry.messages_address(), "https://openrouter.ai/api/v1/messages", "{base}");
606 assert_eq!(entry.api_address(Wire::OpenAi, "/v1/models"), "https://openrouter.ai/api/v1/models", "{base}");
607 }
608 let mut entry = ProviderEntry::new(tag("yol"), ProviderKind::OpenRouter, "https://openrouter.ai");
609 entry.wire = Wire::OpenAi;
610 assert_eq!(entry.messages_address(), "https://openrouter.ai/api/v1/chat/completions");
611 let ollama = ProviderEntry::new(tag("ev"), ProviderKind::Ollama, "http://h:1");
612 assert_eq!(
613 ollama.api_address(Wire::OpenAi, "/v1/models"),
614 "http://h:1/v1/models",
615 "an ollama server answers at its root"
616 );
617 }
618
619 #[test]
620 fn every_written_value_reads_back_as_itself() {
621 for kind in ProviderKind::ALL {
622 assert_eq!(ProviderKind::parse(kind.id()), Some(kind));
623 }
624 for wire in Wire::ALL {
625 assert_eq!(Wire::parse(wire.id()), Some(wire));
626 }
627 for measured in [Measured::About(7), Measured::AtLeast(7)] {
628 assert_eq!(Measured::parse(measured.id(), 7), Some(measured));
629 }
630 for price in Price::ALL {
631 assert_eq!(Price::parse(price.id()), Some(price));
632 }
633 assert_eq!(ProviderKind::parse("ollamaa"), None);
634 assert_eq!(Wire::parse("anthropics"), None);
635 assert_eq!(Measured::parse("exactly", 7), None);
636 assert_eq!(Price::parse("cheaper"), None);
637 }
638
639 fn with_windows() -> ProviderEntry {
643 let mut entry =
644 ProviderEntry::new(tag("mimo"), ProviderKind::MimoTokenPlan, "https://token-plan-sgp.xiaomimimo.com");
645 entry.models = vec![
646 Model { id: "mimo-v2.6-pro".to_owned(), claimed: Some(131_072), measured: None, price: None },
647 Model { id: "mimo-v2.5".to_owned(), claimed: Some(32_768), measured: None, price: None },
648 Model { id: "mimo-v2.5-asr".to_owned(), claimed: None, measured: None, price: None },
649 ];
650 entry
651 }
652
653 #[test]
654 fn an_order_is_promised_the_smallest_window_of_its_steps_because_a_conversation_may_go_on_on_any_of_them() {
655 let entry = with_windows();
656 let lineup = Lineup {
657 name: tag("kisa"),
658 models: vec!["mimo-v2.6-pro".to_owned(), "mimo-v2.5".to_owned(), "mimo-v2.5-asr".to_owned()],
659 };
660 assert_eq!(lineup.window(&entry), Some(32_768), "what is promised has to hold for the step that answers");
661 assert_eq!(lineup.unknown_window(&entry), ["mimo-v2.5-asr"], "and the one nobody knows a window for is named");
662
663 let unknown = Lineup { name: tag("geci"), models: vec!["mimo-v2.5-asr".to_owned(), "yok".to_owned()] };
664 assert_eq!(unknown.window(&entry), None, "no step with a known window is no window at all");
665 assert_eq!(
666 unknown.unknown_window(&entry),
667 ["mimo-v2.5-asr", "yok"],
668 "a step naming an unknown model is one too"
669 );
670 }
671
672 #[test]
673 fn an_order_names_exactly_the_steps_that_spend_money_and_says_nothing_about_a_price_it_did_not_read() {
674 let mut entry = ProviderEntry::new(tag("yol"), ProviderKind::OpenRouter, "https://openrouter.ai");
675 entry.models = vec![
676 Model { id: "z-ai/glm-4.6".to_owned(), claimed: None, measured: None, price: Some(Price::Paid) },
677 Model { id: "z-ai/glm-4.6:free".to_owned(), claimed: None, measured: None, price: Some(Price::Free) },
678 Model { id: "sirala".to_owned(), claimed: None, measured: None, price: None },
679 ];
680 let lineup = Lineup {
681 name: tag("coder"),
682 models: vec!["z-ai/glm-4.6:free".to_owned(), "z-ai/glm-4.6".to_owned(), "sirala".to_owned()],
683 };
684 assert_eq!(lineup.paid(&entry), ["z-ai/glm-4.6"], "only the step that costs, not the free one beside it");
685 }
686
687 #[test]
688 fn an_order_is_found_under_the_name_it_was_given_and_a_provider_of_no_orders_has_none() {
689 let mut entry = with_windows();
690 let lineup = Lineup { name: tag("coder"), models: vec!["mimo-v2.5".to_owned()] };
691 entry.lineups.push(lineup.clone());
692 assert_eq!(entry.lineup("coder"), Some(&lineup));
693 assert_eq!(entry.lineup("yok"), None, "a name nobody gave");
694 assert!(ProviderEntry::new(tag("ev"), ProviderKind::Ollama, "http://h:1").lineups.is_empty());
695 }
696
697 #[test]
698 fn only_a_window_whose_edge_was_found_is_called_cramped() {
699 assert!(Measured::About(3_012).is_cramped(), "the edge was found and it is small");
700 assert!(!Measured::AtLeast(3_012).is_cramped(), "a probe that never stopped growing says nothing yet");
701 assert!(!Measured::About(65_536).is_cramped());
702 assert_eq!(Measured::About(3_012).tokens(), 3_012);
703 }
704
705 #[test]
706 fn a_server_giving_far_less_than_the_model_claims_is_what_the_person_is_told() {
707 let short = Model {
709 id: "qwen3.8".to_owned(),
710 claimed: Some(262_144),
711 measured: Some(Measured::About(3_012)),
712 price: None,
713 };
714 assert!(short.is_short_changed());
715 let honest =
716 Model { id: "q".to_owned(), claimed: Some(32_768), measured: Some(Measured::AtLeast(32_768)), price: None };
717 assert!(!honest.is_short_changed());
718 assert!(!Model::new("q").is_short_changed(), "nothing is claimed about a model nobody asked about");
719 }
720
721 #[test]
722 fn only_a_local_server_is_reached_without_a_key() {
723 assert!(!ProviderKind::Ollama.needs_key(), "a local server carries no secret into a container");
724 assert!(ProviderKind::OpenRouter.needs_key());
725 assert!(ProviderKind::MimoTokenPlan.needs_key());
726 assert!(ProviderKind::KimiCode.needs_key());
727 }
728
729 #[test]
730 fn xiaomi_is_asked_under_anthropic_in_one_shape_and_at_its_root_in_the_other() {
731 for base in [
734 "https://token-plan-ams.xiaomimimo.com",
735 "https://token-plan-ams.xiaomimimo.com/",
736 "https://token-plan-ams.xiaomimimo.com/anthropic",
737 "https://token-plan-ams.xiaomimimo.com/v1",
738 "https://token-plan-ams.xiaomimimo.com/anthropic/v1/",
739 ] {
740 let entry = ProviderEntry::new(tag("mimo"), ProviderKind::MimoTokenPlan, base);
741 assert_eq!(
742 entry.messages_address(),
743 "https://token-plan-ams.xiaomimimo.com/anthropic/v1/messages",
744 "{base}"
745 );
746 assert_eq!(
747 entry.api_address(Wire::OpenAi, "/v1/chat/completions"),
748 "https://token-plan-ams.xiaomimimo.com/v1/chat/completions",
749 "{base}"
750 );
751 assert_eq!(
752 entry.api_address(Wire::of_path("/v1/models"), "/v1/models"),
753 "https://token-plan-ams.xiaomimimo.com/v1/models",
754 "{base}: the listing is only at the root"
755 );
756 }
757 }
758
759 #[test]
760 fn kimi_is_asked_under_coding_in_both_shapes() {
761 for base in ["https://api.kimi.com", "https://api.kimi.com/coding", "https://api.kimi.com/coding/v1"] {
762 let entry = ProviderEntry::new(tag("kimi"), ProviderKind::KimiCode, base);
763 assert_eq!(entry.messages_address(), "https://api.kimi.com/coding/v1/messages", "{base}");
764 assert_eq!(
765 entry.api_address(Wire::OpenAi, "/v1/chat/completions"),
766 "https://api.kimi.com/coding/v1/chat/completions",
767 "{base}"
768 );
769 }
770 }
771
772 #[test]
773 fn a_harness_says_its_shape_by_the_path_it_asks_for() {
774 assert_eq!(Wire::of_path("/v1/messages"), Wire::Anthropic);
775 assert_eq!(Wire::of_path("/v1/messages?beta=true"), Wire::Anthropic, "what Claude Code really sends");
776 assert_eq!(Wire::of_path("/v1/chat/completions"), Wire::OpenAi);
777 assert_eq!(Wire::of_path("/v1/models"), Wire::OpenAi, "the one root every kind lists at");
778 }
779
780 #[test]
781 fn each_kind_puts_its_key_in_the_header_its_maker_names() {
782 assert_eq!(ProviderKind::Ollama.key_header(), ("Authorization", "Bearer "));
783 assert_eq!(ProviderKind::OpenRouter.key_header(), ("Authorization", "Bearer "));
784 assert_eq!(ProviderKind::MimoTokenPlan.key_header(), ("api-key", ""));
785 assert_eq!(ProviderKind::KimiCode.key_header(), ("x-api-key", ""));
786 }
787
788 #[test]
789 fn a_ready_made_kind_starts_with_its_makers_models_and_a_kind_of_ones_own_with_none() {
790 let mimo =
791 ProviderEntry::new(tag("mimo"), ProviderKind::MimoTokenPlan, ProviderKind::MimoTokenPlan.suggested_base());
792 let ids: Vec<&str> = mimo.models.iter().map(|model| model.id.as_str()).collect();
793 assert_eq!(ids, ["mimo-v2.6-pro", "mimo-v2.6-flash", "mimo-v2.5-pro", "mimo-v2.5"]);
794 let flash = mimo.model("mimo-v2.6-flash").expect("the model");
795 assert_eq!(flash.claimed, Some(1_048_576));
796 assert!(
797 flash
798 .published_at(ProviderKind::MimoTokenPlan)
799 .is_some_and(|source| source.starts_with("https://mimo.mi.com/"))
800 );
801 let kimi = ProviderEntry::new(tag("kimi"), ProviderKind::KimiCode, ProviderKind::KimiCode.suggested_base());
802 assert_eq!(kimi.model("kimi-for-coding").and_then(|model| model.claimed), Some(1_048_576));
803 assert!(ProviderEntry::new(tag("ev"), ProviderKind::Ollama, "http://h:1").models.is_empty());
804 }
805
806 #[test]
807 fn a_harness_is_told_a_claimed_window_only_where_the_claim_is_what_is_served() {
808 let claimed = Model { id: "m".to_owned(), claimed: Some(262_144), measured: None, price: None };
809 assert_eq!(claimed.window(ProviderKind::Ollama), None);
811 assert_eq!(claimed.window(ProviderKind::OpenRouter), None);
812 assert_eq!(claimed.window(ProviderKind::MimoTokenPlan), Some(262_144));
813 assert_eq!(claimed.window(ProviderKind::KimiCode), Some(262_144));
814 let measured = Model { measured: Some(Measured::About(31_512)), ..claimed };
815 for kind in ProviderKind::ALL {
816 assert_eq!(measured.window(kind), Some(31_512), "{kind:?}: what was measured comes first");
817 }
818 }
819
820 #[test]
821 fn every_region_offered_is_an_address_of_its_own_kind() {
822 for kind in ProviderKind::ALL {
823 for region in kind.regions() {
824 assert!(region.base.starts_with("https://"), "{kind:?} {region:?}");
825 assert_eq!(trim_base(region.base), region.base, "{kind:?}: no trailing slash");
826 }
827 if let Some(first) = kind.regions().first() {
828 assert_eq!(first.base, kind.suggested_base(), "{kind:?}: the offered address is the first region");
829 }
830 }
831 assert_eq!(ProviderKind::MimoTokenPlan.regions().len(), 3);
832 assert_eq!(ProviderKind::KimiCode.suggested_base(), "https://api.kimi.com");
833 }
834}