#[derive(Debug, Clone, Copy)]
pub struct PremiumPricingTier {
pub input_threshold: u32,
pub price_input_per_m: f64,
pub price_output_per_m: f64,
}
#[derive(Debug, Clone, Copy)]
pub struct ModelInfo {
pub context_window: u32,
pub auto_compact_token_limit: Option<u32>,
pub requires_adaptive_thinking: bool,
pub supports_server_compaction: bool,
pub price_input_per_m: f64,
pub price_output_per_m: f64,
pub premium_tier: Option<PremiumPricingTier>,
}
impl Default for ModelInfo {
fn default() -> Self {
Self {
context_window: 200_000,
auto_compact_token_limit: Some(170_000),
requires_adaptive_thinking: false,
supports_server_compaction: false,
price_input_per_m: 3.0,
price_output_per_m: 15.0,
premium_tier: None,
}
}
}
impl ModelInfo {
pub fn auto_compact_at(&self) -> u32 {
let api_ceiling = ((self.context_window as u64).saturating_mul(9) / 10) as u32;
match self.auto_compact_token_limit {
Some(limit) => limit.min(api_ceiling),
None => api_ceiling,
}
}
pub fn effective_window(&self) -> u32 {
((self.context_window as u64).saturating_mul(95) / 100) as u32
}
}
pub fn lookup(model: &str) -> ModelInfo {
let m = model.to_ascii_lowercase();
if m.starts_with("claude-opus-4-7") {
return ModelInfo {
context_window: 1_000_000,
auto_compact_token_limit: Some(250_000),
requires_adaptive_thinking: true,
supports_server_compaction: true,
price_input_per_m: 5.0,
price_output_per_m: 25.0,
premium_tier: None,
};
}
if m.starts_with("claude-opus-4-6") {
return ModelInfo {
context_window: 1_000_000,
auto_compact_token_limit: Some(250_000),
requires_adaptive_thinking: false,
supports_server_compaction: true,
price_input_per_m: 5.0,
price_output_per_m: 25.0,
premium_tier: None,
};
}
if m.starts_with("claude-sonnet-4-6") {
return ModelInfo {
context_window: 1_000_000,
auto_compact_token_limit: Some(250_000),
requires_adaptive_thinking: false,
supports_server_compaction: true,
price_input_per_m: 3.0,
price_output_per_m: 15.0,
premium_tier: None,
};
}
if m.starts_with("claude-haiku-4-5") {
return ModelInfo {
context_window: 200_000,
auto_compact_token_limit: Some(170_000),
requires_adaptive_thinking: false,
supports_server_compaction: false,
price_input_per_m: 1.0,
price_output_per_m: 5.0,
premium_tier: None,
};
}
if m.contains("codex") {
return ModelInfo {
context_window: 400_000,
auto_compact_token_limit: Some(250_000),
requires_adaptive_thinking: false,
supports_server_compaction: false,
price_input_per_m: 1.75,
price_output_per_m: 14.0,
premium_tier: None,
};
}
if m.starts_with("gpt-5.4") {
return ModelInfo {
context_window: 1_050_000,
auto_compact_token_limit: Some(250_000),
requires_adaptive_thinking: false,
supports_server_compaction: false,
price_input_per_m: 2.5,
price_output_per_m: 15.0,
premium_tier: Some(PremiumPricingTier {
input_threshold: 272_000,
price_input_per_m: 5.0,
price_output_per_m: 22.5,
}),
};
}
if m.starts_with("gpt-5.5") {
return ModelInfo {
context_window: 1_050_000,
auto_compact_token_limit: Some(250_000),
requires_adaptive_thinking: false,
supports_server_compaction: false,
price_input_per_m: 5.0,
price_output_per_m: 30.0,
premium_tier: Some(PremiumPricingTier {
input_threshold: 272_000,
price_input_per_m: 10.0,
price_output_per_m: 45.0,
}),
};
}
ModelInfo::default()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn opus_4_7_has_1m_context_and_server_compaction() {
let info = lookup("claude-opus-4-7");
assert_eq!(info.context_window, 1_000_000);
assert!(info.requires_adaptive_thinking);
assert!(info.supports_server_compaction);
}
#[test]
fn lookup_matches_versioned_opus_4_7_ids() {
assert!(lookup("claude-opus-4-7").requires_adaptive_thinking);
assert!(lookup("claude-opus-4-7-20260301").requires_adaptive_thinking);
assert!(lookup("Claude-Opus-4-7").requires_adaptive_thinking);
}
#[test]
fn lookup_distinguishes_opus_4_6_from_4_7() {
assert!(!lookup("claude-opus-4-6").requires_adaptive_thinking);
assert!(lookup("claude-opus-4-7").requires_adaptive_thinking);
}
#[test]
fn unknown_model_falls_back_to_sonnet_class_pricing() {
let info = lookup("some-future-model-2099");
assert_eq!(info.price_input_per_m, 3.0);
assert_eq!(info.price_output_per_m, 15.0);
}
#[test]
fn auto_compact_at_clamps_override_against_api_ceiling() {
let info = ModelInfo {
context_window: 100_000,
auto_compact_token_limit: Some(200_000),
..ModelInfo::default()
};
assert_eq!(info.auto_compact_at(), 90_000);
}
#[test]
fn auto_compact_at_falls_back_to_90pct_when_unset() {
let info = ModelInfo {
context_window: 200_000,
auto_compact_token_limit: None,
..ModelInfo::default()
};
assert_eq!(info.auto_compact_at(), 180_000);
}
#[test]
fn effective_window_reserves_5pct_headroom() {
let info = ModelInfo {
context_window: 1_000_000,
..ModelInfo::default()
};
assert_eq!(info.effective_window(), 950_000);
}
#[test]
fn cliff_models_compact_below_272k_premium_threshold() {
for slug in ["gpt-5.5", "gpt-5.4"] {
let info = lookup(slug);
assert!(info.auto_compact_at() < 272_000);
let tier = info
.premium_tier
.expect("cliff models carry a premium tier");
assert_eq!(tier.input_threshold, 272_000);
assert!(tier.price_input_per_m > info.price_input_per_m);
}
}
#[test]
fn anthropic_1m_models_advertise_server_compaction() {
for slug in ["claude-opus-4-7", "claude-opus-4-6", "claude-sonnet-4-6"] {
assert!(
lookup(slug).supports_server_compaction,
"{slug} should opt into server-side compaction"
);
}
}
#[test]
fn haiku_does_not_advertise_server_compaction() {
assert!(!lookup("claude-haiku-4-5").supports_server_compaction);
}
}