1#[derive(Debug, Clone, PartialEq)]
87pub struct Model {
88 pub name: &'static str,
90
91 pub family: &'static str,
93
94 pub version: &'static str,
96
97 pub anthropic_id: &'static str,
99
100 pub bedrock_id: Option<&'static str>,
102
103 pub bedrock_global_id: Option<&'static str>,
108
109 pub vertex_id: Option<&'static str>,
111
112 pub max_context_tokens: u32,
114
115 pub max_context_tokens_extended: Option<u32>,
121
122 pub max_output_tokens: u32,
124
125 pub supports_vision: bool,
127
128 pub supports_tools: bool,
130
131 pub supports_caching: bool,
133
134 pub supports_extended_thinking: bool,
136
137 pub supports_effort: bool,
142
143 pub cost_per_mtok_input: f64,
145
146 pub cost_per_mtok_output: f64,
148
149 pub description: &'static str,
151}
152
153#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub enum BedrockRegion {
173 Standard,
180
181 Global,
189
190 US,
197
198 EU,
205
206 AsiaPacific,
213}
214
215impl BedrockRegion {
216 pub fn prefix(&self) -> &'static str {
218 match self {
219 BedrockRegion::Standard => "",
220 BedrockRegion::Global => "global.",
221 BedrockRegion::US => "us.",
222 BedrockRegion::EU => "eu.",
223 BedrockRegion::AsiaPacific => "ap.",
224 }
225 }
226}
227
228impl Model {
229 pub fn anthropic_id(&self) -> &'static str {
231 self.anthropic_id
232 }
233
234 pub fn bedrock_id(&self) -> Option<&'static str> {
236 self.bedrock_id
237 }
238
239 pub fn bedrock_id_for_region(&self, region: BedrockRegion) -> Option<String> {
259 self.bedrock_id.map(|id| {
260 let prefix = region.prefix();
261 if prefix.is_empty() {
262 id.to_string()
263 } else {
264 format!("{}{}", prefix, id)
265 }
266 })
267 }
268
269 pub fn bedrock_global_id(&self) -> Option<&'static str> {
276 self.bedrock_global_id
277 }
278
279 pub fn vertex_id(&self) -> Option<&'static str> {
281 self.vertex_id
282 }
283
284 pub fn supports_extended_context(&self) -> bool {
286 self.max_context_tokens_extended.is_some()
287 }
288
289 pub fn max_extended_context(&self) -> Option<u32> {
301 self.max_context_tokens_extended
302 }
303
304 pub fn validate_request(
310 &self,
311 max_tokens: u32,
312 use_extended_context: bool,
313 ) -> Result<(), String> {
314 if max_tokens > self.max_output_tokens {
315 return Err(format!(
316 "Requested max_tokens ({}) exceeds model limit ({})",
317 max_tokens, self.max_output_tokens
318 ));
319 }
320
321 if use_extended_context && !self.supports_extended_context() {
322 return Err(format!(
323 "Model {} does not support extended context",
324 self.name
325 ));
326 }
327
328 Ok(())
329 }
330
331 pub fn estimate_cost(&self, input_tokens: u32, output_tokens: u32) -> f64 {
333 (input_tokens as f64 / 1_000_000.0) * self.cost_per_mtok_input
334 + (output_tokens as f64 / 1_000_000.0) * self.cost_per_mtok_output
335 }
336}
337
338pub const CLAUDE_SONNET_4_5: Model = Model {
349 name: "Claude Sonnet 4.5",
350 family: "sonnet",
351 version: "2025-09-29",
352 anthropic_id: "claude-sonnet-4-5-20250929",
353 bedrock_id: Some("anthropic.claude-sonnet-4-5-20250929-v1:0"),
354 bedrock_global_id: Some("global.anthropic.claude-sonnet-4-5-20250929-v1:0"),
355 vertex_id: Some("claude-sonnet-4-5@20250929"),
356 max_context_tokens: 200_000,
357 max_context_tokens_extended: Some(1_000_000),
358 max_output_tokens: 64_000,
359 supports_vision: true,
360 supports_tools: true,
361 supports_caching: true,
362 supports_extended_thinking: true,
363 supports_effort: false,
364 cost_per_mtok_input: 3.0,
365 cost_per_mtok_output: 15.0,
366 description: "Smart model for complex agents and coding",
367};
368
369pub const CLAUDE_HAIKU_4_5: Model = Model {
373 name: "Claude Haiku 4.5",
374 family: "haiku",
375 version: "2025-10-01",
376 anthropic_id: "claude-haiku-4-5-20251001",
377 bedrock_id: Some("anthropic.claude-haiku-4-5-20251001-v1:0"),
378 bedrock_global_id: Some("global.anthropic.claude-haiku-4-5-20251001-v1:0"),
379 vertex_id: Some("claude-haiku-4-5@20251001"),
380 max_context_tokens: 200_000,
381 max_context_tokens_extended: None,
382 max_output_tokens: 64_000,
383 supports_vision: true,
384 supports_tools: true,
385 supports_caching: true,
386 supports_extended_thinking: true,
387 supports_effort: false,
388 cost_per_mtok_input: 1.0,
389 cost_per_mtok_output: 5.0,
390 description: "Fastest model with near-frontier intelligence",
391};
392
393pub const CLAUDE_OPUS_4_5: Model = Model {
398 name: "Claude Opus 4.5",
399 family: "opus",
400 version: "2025-11-01",
401 anthropic_id: "claude-opus-4-5-20251101",
402 bedrock_id: Some("anthropic.claude-opus-4-5-20251101-v1:0"),
403 bedrock_global_id: Some("global.anthropic.claude-opus-4-5-20251101-v1:0"),
404 vertex_id: Some("claude-opus-4-5@20251101"),
405 max_context_tokens: 200_000,
406 max_context_tokens_extended: None,
407 max_output_tokens: 64_000,
408 supports_vision: true,
409 supports_tools: true,
410 supports_caching: true,
411 supports_extended_thinking: true,
412 supports_effort: true, cost_per_mtok_input: 5.0,
414 cost_per_mtok_output: 25.0,
415 description: "Maximum intelligence with practical performance",
416};
417
418pub const CLAUDE_SONNET_4_6: Model = Model {
423 name: "Claude Sonnet 4.6",
424 family: "sonnet",
425 version: "2026-01-15",
426 anthropic_id: "claude-sonnet-4-6-20260115",
427 bedrock_id: Some("anthropic.claude-sonnet-4-6-20260115-v1:0"),
428 bedrock_global_id: Some("global.anthropic.claude-sonnet-4-6-20260115-v1:0"),
429 vertex_id: Some("claude-sonnet-4-6@20260115"),
430 max_context_tokens: 200_000,
431 max_context_tokens_extended: Some(1_000_000),
432 max_output_tokens: 64_000,
433 supports_vision: true,
434 supports_tools: true,
435 supports_caching: true,
436 supports_extended_thinking: true,
437 supports_effort: false,
438 cost_per_mtok_input: 3.0,
439 cost_per_mtok_output: 15.0,
440 description: "Best balance of speed and intelligence",
441};
442
443pub const CLAUDE_OPUS_4_6: Model = Model {
448 name: "Claude Opus 4.6",
449 family: "opus",
450 version: "2026-02-01",
451 anthropic_id: "claude-opus-4-6-20260201",
452 bedrock_id: Some("anthropic.claude-opus-4-6-20260201-v1:0"),
453 bedrock_global_id: Some("global.anthropic.claude-opus-4-6-20260201-v1:0"),
454 vertex_id: Some("claude-opus-4-6@20260201"),
455 max_context_tokens: 200_000,
456 max_context_tokens_extended: None,
457 max_output_tokens: 64_000,
458 supports_vision: true,
459 supports_tools: true,
460 supports_caching: true,
461 supports_extended_thinking: true,
462 supports_effort: true,
463 cost_per_mtok_input: 5.0,
464 cost_per_mtok_output: 25.0,
465 description: "Frontier intelligence for long-running agents and coding",
466};
467
468pub const CLAUDE_OPUS_4_7: Model = Model {
473 name: "Claude Opus 4.7",
474 family: "opus",
475 version: "2026-03-01",
476 anthropic_id: "claude-opus-4-7-20260301",
477 bedrock_id: Some("anthropic.claude-opus-4-7-20260301-v1:0"),
478 bedrock_global_id: Some("global.anthropic.claude-opus-4-7-20260301-v1:0"),
479 vertex_id: Some("claude-opus-4-7@20260301"),
480 max_context_tokens: 200_000,
481 max_context_tokens_extended: None,
482 max_output_tokens: 64_000,
483 supports_vision: true,
484 supports_tools: true,
485 supports_caching: true,
486 supports_extended_thinking: true,
487 supports_effort: true,
488 cost_per_mtok_input: 5.0,
489 cost_per_mtok_output: 25.0,
490 description: "Frontier intelligence for long-running agents and coding",
491};
492
493pub const CLAUDE_OPUS_4_8: Model = Model {
498 name: "Claude Opus 4.8",
499 family: "opus",
500 version: "2026-04-01",
501 anthropic_id: "claude-opus-4-8-20260401",
502 bedrock_id: Some("anthropic.claude-opus-4-8-20260401-v1:0"),
503 bedrock_global_id: Some("global.anthropic.claude-opus-4-8-20260401-v1:0"),
504 vertex_id: Some("claude-opus-4-8@20260401"),
505 max_context_tokens: 200_000,
506 max_context_tokens_extended: None,
507 max_output_tokens: 64_000,
508 supports_vision: true,
509 supports_tools: true,
510 supports_caching: true,
511 supports_extended_thinking: true,
512 supports_effort: true,
513 cost_per_mtok_input: 5.0,
514 cost_per_mtok_output: 25.0,
515 description: "Frontier intelligence for long-running agents and coding",
516};
517
518pub const CLAUDE_FABLE_5: Model = Model {
527 name: "Claude Fable 5",
528 family: "fable",
529 version: "2026-06-01",
530 anthropic_id: "claude-fable-5-20260601",
531 bedrock_id: Some("anthropic.claude-fable-5-20260601-v1:0"),
532 bedrock_global_id: Some("global.anthropic.claude-fable-5-20260601-v1:0"),
533 vertex_id: Some("claude-fable-5@20260601"),
534 max_context_tokens: 200_000,
535 max_context_tokens_extended: Some(1_000_000),
536 max_output_tokens: 64_000,
537 supports_vision: true,
538 supports_tools: true,
539 supports_caching: true,
540 supports_extended_thinking: true,
541 supports_effort: false,
542 cost_per_mtok_input: 3.0,
543 cost_per_mtok_output: 15.0,
544 description: "Next generation for hardest knowledge work and coding",
545};
546
547pub const CLAUDE_MYTHOS_5: Model = Model {
552 name: "Claude Mythos 5",
553 family: "mythos",
554 version: "2026-06-15",
555 anthropic_id: "claude-mythos-5-20260615",
556 bedrock_id: Some("anthropic.claude-mythos-5-20260615-v1:0"),
557 bedrock_global_id: Some("global.anthropic.claude-mythos-5-20260615-v1:0"),
558 vertex_id: Some("claude-mythos-5@20260615"),
559 max_context_tokens: 200_000,
560 max_context_tokens_extended: Some(1_000_000),
561 max_output_tokens: 64_000,
562 supports_vision: true,
563 supports_tools: true,
564 supports_caching: true,
565 supports_extended_thinking: true,
566 supports_effort: false,
567 cost_per_mtok_input: 5.0,
568 cost_per_mtok_output: 25.0,
569 description: "Most capable for cybersecurity and biology research",
570};
571
572pub const CLAUDE_OPUS_4_1: Model = Model {
578 name: "Claude Opus 4.1",
579 family: "opus",
580 version: "2025-08-05",
581 anthropic_id: "claude-opus-4-1-20250805",
582 bedrock_id: Some("anthropic.claude-opus-4-1-20250805-v1:0"),
583 bedrock_global_id: None,
584 vertex_id: Some("claude-opus-4-1@20250805"),
585 max_context_tokens: 200_000,
586 max_context_tokens_extended: None,
587 max_output_tokens: 32_000,
588 supports_vision: true,
589 supports_tools: true,
590 supports_caching: true,
591 supports_extended_thinking: true,
592 supports_effort: false,
593 cost_per_mtok_input: 15.0,
594 cost_per_mtok_output: 75.0,
595 description: "Previous generation powerful model",
596};
597
598pub const CLAUDE_SONNET_4: Model = Model {
602 name: "Claude Sonnet 4",
603 family: "sonnet",
604 version: "2025-05-14",
605 anthropic_id: "claude-sonnet-4-20250514",
606 bedrock_id: Some("anthropic.claude-sonnet-4-20250514-v1:0"),
607 bedrock_global_id: None,
608 vertex_id: Some("claude-sonnet-4@20250514"),
609 max_context_tokens: 200_000,
610 max_context_tokens_extended: Some(1_000_000),
611 max_output_tokens: 64_000,
612 supports_vision: true,
613 supports_tools: true,
614 supports_caching: true,
615 supports_extended_thinking: true,
616 supports_effort: false,
617 cost_per_mtok_input: 3.0,
618 cost_per_mtok_output: 15.0,
619 description: "Previous generation balanced model",
620};
621
622pub const CLAUDE_SONNET_3_7: Model = Model {
626 name: "Claude Sonnet 3.7",
627 family: "sonnet",
628 version: "2025-02-19",
629 anthropic_id: "claude-3-7-sonnet-20250219",
630 bedrock_id: Some("anthropic.claude-3-7-sonnet-20250219-v1:0"),
631 bedrock_global_id: None,
632 vertex_id: Some("claude-3-7-sonnet@20250219"),
633 max_context_tokens: 200_000,
634 max_context_tokens_extended: None,
635 max_output_tokens: 64_000, supports_vision: true,
637 supports_tools: true,
638 supports_caching: true,
639 supports_extended_thinking: true,
640 supports_effort: false,
641 cost_per_mtok_input: 3.0,
642 cost_per_mtok_output: 15.0,
643 description: "Claude 3.7 balanced model",
644};
645
646pub const CLAUDE_OPUS_4: Model = Model {
648 name: "Claude Opus 4",
649 family: "opus",
650 version: "2025-05-14",
651 anthropic_id: "claude-opus-4-20250514",
652 bedrock_id: Some("anthropic.claude-opus-4-20250514-v1:0"),
653 bedrock_global_id: None,
654 vertex_id: Some("claude-opus-4@20250514"),
655 max_context_tokens: 200_000,
656 max_context_tokens_extended: None,
657 max_output_tokens: 32_000,
658 supports_vision: true,
659 supports_tools: true,
660 supports_caching: true,
661 supports_extended_thinking: true,
662 supports_effort: false,
663 cost_per_mtok_input: 15.0,
664 cost_per_mtok_output: 75.0,
665 description: "Claude 4 powerful model",
666};
667
668pub const CLAUDE_HAIKU_3_5: Model = Model {
670 name: "Claude Haiku 3.5",
671 family: "haiku",
672 version: "2024-10-22",
673 anthropic_id: "claude-3-5-haiku-20241022",
674 bedrock_id: Some("anthropic.claude-3-5-haiku-20241022-v1:0"),
675 bedrock_global_id: None,
676 vertex_id: Some("claude-3-5-haiku@20241022"),
677 max_context_tokens: 200_000,
678 max_context_tokens_extended: None,
679 max_output_tokens: 8_192,
680 supports_vision: true,
681 supports_tools: true,
682 supports_caching: true,
683 supports_extended_thinking: false,
684 supports_effort: false,
685 cost_per_mtok_input: 0.80,
686 cost_per_mtok_output: 4.0,
687 description: "Fast and efficient model",
688};
689
690pub const CLAUDE_HAIKU_3: Model = Model {
692 name: "Claude Haiku 3",
693 family: "haiku",
694 version: "2024-03-07",
695 anthropic_id: "claude-3-haiku-20240307",
696 bedrock_id: Some("anthropic.claude-3-haiku-20240307-v1:0"),
697 bedrock_global_id: None,
698 vertex_id: Some("claude-3-haiku@20240307"),
699 max_context_tokens: 200_000,
700 max_context_tokens_extended: None,
701 max_output_tokens: 4_096,
702 supports_vision: true,
703 supports_tools: true,
704 supports_caching: true,
705 supports_extended_thinking: false,
706 supports_effort: false,
707 cost_per_mtok_input: 0.25,
708 cost_per_mtok_output: 1.25,
709 description: "Original fast model",
710};
711
712pub const ALL_MODELS: &[&Model] = &[
714 &CLAUDE_MYTHOS_5,
716 &CLAUDE_FABLE_5,
717 &CLAUDE_OPUS_4_8,
719 &CLAUDE_OPUS_4_7,
720 &CLAUDE_OPUS_4_6,
721 &CLAUDE_SONNET_4_6,
722 &CLAUDE_SONNET_4_5,
724 &CLAUDE_HAIKU_4_5,
725 &CLAUDE_OPUS_4_5,
726 &CLAUDE_OPUS_4_1,
728 &CLAUDE_SONNET_4,
729 &CLAUDE_SONNET_3_7,
730 &CLAUDE_OPUS_4,
731 &CLAUDE_HAIKU_3_5,
732 &CLAUDE_HAIKU_3,
733];
734
735pub fn get_model_by_anthropic_id(id: &str) -> Option<&'static Model> {
752 ALL_MODELS.iter().find(|m| m.anthropic_id == id).copied()
753}
754
755pub fn get_model_by_bedrock_id(id: &str) -> Option<&'static Model> {
783 if let Some(model) = ALL_MODELS
785 .iter()
786 .find(|m| m.bedrock_id == Some(id) || m.bedrock_global_id == Some(id))
787 {
788 return Some(*model);
789 }
790
791 let base_id = id
793 .strip_prefix("global.")
794 .or_else(|| id.strip_prefix("us."))
795 .or_else(|| id.strip_prefix("eu."))
796 .or_else(|| id.strip_prefix("ap."))
797 .unwrap_or(id);
798
799 ALL_MODELS
800 .iter()
801 .find(|m| m.bedrock_id == Some(base_id))
802 .copied()
803}
804
805pub fn get_model_by_vertex_id(id: &str) -> Option<&'static Model> {
819 ALL_MODELS.iter().find(|m| m.vertex_id == Some(id)).copied()
820}
821
822pub fn get_model(id: &str) -> Option<&'static Model> {
853 get_model_by_anthropic_id(id)
854 .or_else(|| get_model_by_bedrock_id(id))
855 .or_else(|| get_model_by_vertex_id(id))
856}
857
858#[cfg(test)]
859#[allow(clippy::assertions_on_constants)]
860mod tests {
861 use super::*;
862
863 #[test]
864 fn test_model_constants() {
865 assert_eq!(CLAUDE_SONNET_4_5.anthropic_id, "claude-sonnet-4-5-20250929");
866 assert_eq!(
867 CLAUDE_SONNET_4_5.bedrock_id,
868 Some("anthropic.claude-sonnet-4-5-20250929-v1:0")
869 );
870 assert_eq!(
871 CLAUDE_SONNET_4_5.bedrock_global_id,
872 Some("global.anthropic.claude-sonnet-4-5-20250929-v1:0")
873 );
874 assert_eq!(
875 CLAUDE_SONNET_4_5.max_context_tokens_extended,
876 Some(1_000_000)
877 );
878 }
879
880 #[test]
881 fn test_model_lookup_anthropic() {
882 let model = get_model_by_anthropic_id("claude-sonnet-4-5-20250929");
883 assert!(model.is_some());
884 assert_eq!(model.unwrap().name, "Claude Sonnet 4.5");
885 }
886
887 #[test]
888 fn test_model_lookup_bedrock_regional() {
889 let model = get_model_by_bedrock_id("anthropic.claude-sonnet-4-5-20250929-v1:0");
890 assert!(model.is_some());
891 assert_eq!(model.unwrap().name, "Claude Sonnet 4.5");
892 }
893
894 #[test]
895 fn test_model_lookup_bedrock_global() {
896 let model = get_model_by_bedrock_id("global.anthropic.claude-sonnet-4-5-20250929-v1:0");
897 assert!(model.is_some());
898 assert_eq!(model.unwrap().name, "Claude Sonnet 4.5");
899 }
900
901 #[test]
902 fn test_model_lookup_bedrock_us() {
903 let model = get_model_by_bedrock_id("us.anthropic.claude-sonnet-4-5-20250929-v1:0");
904 assert!(model.is_some());
905 assert_eq!(model.unwrap().name, "Claude Sonnet 4.5");
906 }
907
908 #[test]
909 fn test_model_lookup_bedrock_eu() {
910 let model = get_model_by_bedrock_id("eu.anthropic.claude-sonnet-4-5-20250929-v1:0");
911 assert!(model.is_some());
912 assert_eq!(model.unwrap().name, "Claude Sonnet 4.5");
913 }
914
915 #[test]
916 fn test_model_lookup_bedrock_ap() {
917 let model = get_model_by_bedrock_id("ap.anthropic.claude-sonnet-4-5-20250929-v1:0");
918 assert!(model.is_some());
919 assert_eq!(model.unwrap().name, "Claude Sonnet 4.5");
920 }
921
922 #[test]
923 fn test_bedrock_id_for_region() {
924 let regional = CLAUDE_SONNET_4_5.bedrock_id_for_region(BedrockRegion::Standard);
926 assert_eq!(
927 regional.as_deref(),
928 Some("anthropic.claude-sonnet-4-5-20250929-v1:0")
929 );
930
931 let global = CLAUDE_SONNET_4_5.bedrock_id_for_region(BedrockRegion::Global);
933 assert_eq!(
934 global.as_deref(),
935 Some("global.anthropic.claude-sonnet-4-5-20250929-v1:0")
936 );
937
938 let us = CLAUDE_SONNET_4_5.bedrock_id_for_region(BedrockRegion::US);
940 assert_eq!(
941 us.as_deref(),
942 Some("us.anthropic.claude-sonnet-4-5-20250929-v1:0")
943 );
944
945 let eu = CLAUDE_SONNET_4_5.bedrock_id_for_region(BedrockRegion::EU);
947 assert_eq!(
948 eu.as_deref(),
949 Some("eu.anthropic.claude-sonnet-4-5-20250929-v1:0")
950 );
951
952 let ap = CLAUDE_SONNET_4_5.bedrock_id_for_region(BedrockRegion::AsiaPacific);
954 assert_eq!(
955 ap.as_deref(),
956 Some("ap.anthropic.claude-sonnet-4-5-20250929-v1:0")
957 );
958 }
959
960 #[test]
961 fn test_model_lookup_any() {
962 assert!(get_model("claude-sonnet-4-5-20250929").is_some());
964 assert!(get_model("anthropic.claude-sonnet-4-5-20250929-v1:0").is_some());
965 assert!(get_model("global.anthropic.claude-sonnet-4-5-20250929-v1:0").is_some());
966 assert!(get_model("us.anthropic.claude-sonnet-4-5-20250929-v1:0").is_some());
967 assert!(get_model("eu.anthropic.claude-sonnet-4-5-20250929-v1:0").is_some());
968 assert!(get_model("ap.anthropic.claude-sonnet-4-5-20250929-v1:0").is_some());
969 }
970
971 #[test]
972 fn test_validate_request() {
973 assert!(CLAUDE_SONNET_4_5.validate_request(1024, false).is_ok());
974 assert!(CLAUDE_SONNET_4_5.validate_request(64_000, false).is_ok());
975 assert!(CLAUDE_SONNET_4_5.validate_request(100_000, false).is_err());
976
977 assert!(CLAUDE_SONNET_4_5.validate_request(1024, true).is_ok());
979 assert!(CLAUDE_HAIKU_4_5.validate_request(1024, true).is_err()); }
981
982 #[test]
983 fn test_extended_context_support() {
984 assert!(CLAUDE_SONNET_4_5.supports_extended_context());
986 assert_eq!(CLAUDE_SONNET_4_5.max_extended_context(), Some(1_000_000));
987 assert!(CLAUDE_SONNET_4.supports_extended_context());
988
989 assert!(!CLAUDE_HAIKU_4_5.supports_extended_context());
991 assert_eq!(CLAUDE_HAIKU_4_5.max_extended_context(), None);
992 assert!(!CLAUDE_OPUS_4_5.supports_extended_context());
993 }
994
995 #[test]
996 fn test_bedrock_global_endpoints() {
997 assert!(CLAUDE_SONNET_4_5.bedrock_global_id().is_some());
999 assert!(CLAUDE_HAIKU_4_5.bedrock_global_id().is_some());
1000 assert!(CLAUDE_OPUS_4_5.bedrock_global_id().is_some());
1001
1002 assert!(CLAUDE_SONNET_4.bedrock_global_id().is_none());
1004 assert!(CLAUDE_HAIKU_3_5.bedrock_global_id().is_none());
1005 }
1006
1007 #[test]
1008 fn test_estimate_cost() {
1009 let cost = CLAUDE_SONNET_4_5.estimate_cost(1000, 500);
1010 assert!((cost - 0.0105).abs() < 0.0001);
1014 }
1015
1016 #[test]
1017 fn test_all_models_have_unique_ids() {
1018 let mut ids = std::collections::HashSet::new();
1019 for model in ALL_MODELS {
1020 assert!(ids.insert(model.anthropic_id));
1021 }
1022 }
1023
1024 #[test]
1027 fn test_claude_sonnet_4_6_exists() {
1028 assert_eq!(CLAUDE_SONNET_4_6.name, "Claude Sonnet 4.6");
1029 assert_eq!(CLAUDE_SONNET_4_6.family, "sonnet");
1030 assert_eq!(CLAUDE_SONNET_4_6.anthropic_id, "claude-sonnet-4-6-20260115");
1031 assert!(CLAUDE_SONNET_4_6.supports_vision);
1032 assert!(CLAUDE_SONNET_4_6.supports_tools);
1033 assert!(CLAUDE_SONNET_4_6.supports_caching);
1034 assert!(CLAUDE_SONNET_4_6.supports_extended_thinking);
1035 assert!(!CLAUDE_SONNET_4_6.supports_effort);
1036 assert!(CLAUDE_SONNET_4_6.bedrock_global_id.is_some());
1037 }
1038
1039 #[test]
1040 fn test_claude_sonnet_4_6_lookup() {
1041 let model = get_model_by_anthropic_id("claude-sonnet-4-6-20260115");
1042 assert!(model.is_some());
1043 assert_eq!(model.unwrap().name, "Claude Sonnet 4.6");
1044
1045 let model = get_model("claude-sonnet-4-6-20260115");
1046 assert!(model.is_some());
1047 }
1048
1049 #[test]
1050 fn test_claude_opus_4_6_exists() {
1051 assert_eq!(CLAUDE_OPUS_4_6.name, "Claude Opus 4.6");
1052 assert_eq!(CLAUDE_OPUS_4_6.family, "opus");
1053 assert_eq!(CLAUDE_OPUS_4_6.anthropic_id, "claude-opus-4-6-20260201");
1054 assert!(CLAUDE_OPUS_4_6.supports_vision);
1055 assert!(CLAUDE_OPUS_4_6.supports_tools);
1056 assert!(CLAUDE_OPUS_4_6.supports_caching);
1057 assert!(CLAUDE_OPUS_4_6.supports_extended_thinking);
1058 assert!(CLAUDE_OPUS_4_6.supports_effort);
1059 assert!(CLAUDE_OPUS_4_6.bedrock_global_id.is_some());
1060 }
1061
1062 #[test]
1063 fn test_claude_opus_4_6_lookup() {
1064 let model = get_model_by_anthropic_id("claude-opus-4-6-20260201");
1065 assert!(model.is_some());
1066 assert_eq!(model.unwrap().name, "Claude Opus 4.6");
1067
1068 let model = get_model("claude-opus-4-6-20260201");
1069 assert!(model.is_some());
1070 }
1071
1072 #[test]
1073 fn test_claude_opus_4_7_exists() {
1074 assert_eq!(CLAUDE_OPUS_4_7.name, "Claude Opus 4.7");
1075 assert_eq!(CLAUDE_OPUS_4_7.family, "opus");
1076 assert_eq!(CLAUDE_OPUS_4_7.anthropic_id, "claude-opus-4-7-20260301");
1077 assert!(CLAUDE_OPUS_4_7.supports_vision);
1078 assert!(CLAUDE_OPUS_4_7.supports_tools);
1079 assert!(CLAUDE_OPUS_4_7.supports_caching);
1080 assert!(CLAUDE_OPUS_4_7.supports_extended_thinking);
1081 assert!(CLAUDE_OPUS_4_7.supports_effort);
1082 assert!(CLAUDE_OPUS_4_7.bedrock_global_id.is_some());
1083 }
1084
1085 #[test]
1086 fn test_claude_opus_4_7_lookup() {
1087 let model = get_model_by_anthropic_id("claude-opus-4-7-20260301");
1088 assert!(model.is_some());
1089 assert_eq!(model.unwrap().name, "Claude Opus 4.7");
1090
1091 let model = get_model("claude-opus-4-7-20260301");
1092 assert!(model.is_some());
1093 }
1094
1095 #[test]
1096 fn test_claude_opus_4_8_exists() {
1097 assert_eq!(CLAUDE_OPUS_4_8.name, "Claude Opus 4.8");
1098 assert_eq!(CLAUDE_OPUS_4_8.family, "opus");
1099 assert_eq!(CLAUDE_OPUS_4_8.anthropic_id, "claude-opus-4-8-20260401");
1100 assert!(CLAUDE_OPUS_4_8.supports_vision);
1101 assert!(CLAUDE_OPUS_4_8.supports_tools);
1102 assert!(CLAUDE_OPUS_4_8.supports_caching);
1103 assert!(CLAUDE_OPUS_4_8.supports_extended_thinking);
1104 assert!(CLAUDE_OPUS_4_8.supports_effort);
1105 assert!(CLAUDE_OPUS_4_8.bedrock_global_id.is_some());
1106 }
1107
1108 #[test]
1109 fn test_claude_opus_4_8_lookup() {
1110 let model = get_model_by_anthropic_id("claude-opus-4-8-20260401");
1111 assert!(model.is_some());
1112 assert_eq!(model.unwrap().name, "Claude Opus 4.8");
1113
1114 let model = get_model("claude-opus-4-8-20260401");
1115 assert!(model.is_some());
1116 }
1117
1118 #[test]
1119 fn test_claude_fable_5_exists() {
1120 assert_eq!(CLAUDE_FABLE_5.name, "Claude Fable 5");
1121 assert_eq!(CLAUDE_FABLE_5.family, "fable");
1122 assert_eq!(CLAUDE_FABLE_5.anthropic_id, "claude-fable-5-20260601");
1123 assert!(CLAUDE_FABLE_5.supports_vision);
1124 assert!(CLAUDE_FABLE_5.supports_tools);
1125 assert!(CLAUDE_FABLE_5.supports_caching);
1126 assert!(CLAUDE_FABLE_5.supports_extended_thinking);
1127 assert!(!CLAUDE_FABLE_5.supports_effort);
1128 assert!(CLAUDE_FABLE_5.bedrock_global_id.is_some());
1129 assert_eq!(CLAUDE_FABLE_5.max_context_tokens_extended, Some(1_000_000));
1130 }
1131
1132 #[test]
1133 fn test_claude_fable_5_lookup() {
1134 let model = get_model_by_anthropic_id("claude-fable-5-20260601");
1135 assert!(model.is_some());
1136 assert_eq!(model.unwrap().name, "Claude Fable 5");
1137
1138 let model = get_model("claude-fable-5-20260601");
1139 assert!(model.is_some());
1140 }
1141
1142 #[test]
1143 fn test_claude_mythos_5_exists() {
1144 assert_eq!(CLAUDE_MYTHOS_5.name, "Claude Mythos 5");
1145 assert_eq!(CLAUDE_MYTHOS_5.family, "mythos");
1146 assert_eq!(CLAUDE_MYTHOS_5.anthropic_id, "claude-mythos-5-20260615");
1147 assert!(CLAUDE_MYTHOS_5.supports_vision);
1148 assert!(CLAUDE_MYTHOS_5.supports_tools);
1149 assert!(CLAUDE_MYTHOS_5.supports_caching);
1150 assert!(CLAUDE_MYTHOS_5.supports_extended_thinking);
1151 assert!(!CLAUDE_MYTHOS_5.supports_effort);
1152 assert!(CLAUDE_MYTHOS_5.bedrock_global_id.is_some());
1153 assert_eq!(CLAUDE_MYTHOS_5.max_context_tokens_extended, Some(1_000_000));
1154 }
1155
1156 #[test]
1157 fn test_claude_mythos_5_lookup() {
1158 let model = get_model_by_anthropic_id("claude-mythos-5-20260615");
1159 assert!(model.is_some());
1160 assert_eq!(model.unwrap().name, "Claude Mythos 5");
1161
1162 let model = get_model("claude-mythos-5-20260615");
1163 assert!(model.is_some());
1164 }
1165
1166 #[test]
1167 fn test_new_models_in_all_models() {
1168 let ids: Vec<&str> = ALL_MODELS.iter().map(|m| m.anthropic_id).collect();
1169 assert!(ids.contains(&"claude-sonnet-4-6-20260115"));
1170 assert!(ids.contains(&"claude-opus-4-6-20260201"));
1171 assert!(ids.contains(&"claude-opus-4-7-20260301"));
1172 assert!(ids.contains(&"claude-opus-4-8-20260401"));
1173 assert!(ids.contains(&"claude-fable-5-20260601"));
1174 assert!(ids.contains(&"claude-mythos-5-20260615"));
1175 }
1176
1177 #[test]
1178 fn test_new_models_bedrock_global_ids() {
1179 assert!(CLAUDE_SONNET_4_6.bedrock_global_id().is_some());
1180 assert!(CLAUDE_OPUS_4_6.bedrock_global_id().is_some());
1181 assert!(CLAUDE_OPUS_4_7.bedrock_global_id().is_some());
1182 assert!(CLAUDE_OPUS_4_8.bedrock_global_id().is_some());
1183 assert!(CLAUDE_FABLE_5.bedrock_global_id().is_some());
1184 assert!(CLAUDE_MYTHOS_5.bedrock_global_id().is_some());
1185 }
1186}