use std::collections::HashMap;
use std::path::PathBuf;
fn cache_path() -> PathBuf {
aegis_types::paths::config_dir()
.join("model_ctx.json")
}
const MIN_CTX: u32 = 1_000;
const MAX_CTX: u32 = 20_000_000;
pub fn load_learned(model: &str) -> Option<u32> {
let content = std::fs::read_to_string(cache_path()).ok()?;
let map: HashMap<String, u32> = serde_json::from_str(&content).ok()?;
map.get(model).copied()
}
pub fn record_learned(model: &str, tokens: u32) {
if !(MIN_CTX..=MAX_CTX).contains(&tokens) {
return;
}
let path = cache_path();
let mut map: HashMap<String, u32> = std::fs::read_to_string(&path)
.ok()
.and_then(|c| serde_json::from_str(&c).ok())
.unwrap_or_default();
if map.get(model) == Some(&tokens) {
return; }
map.insert(model.to_string(), tokens);
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
if let Ok(s) = serde_json::to_string_pretty(&map) {
let _ = std::fs::write(&path, s);
}
}
pub fn parse_context_limit(err: &str) -> Option<u32> {
let lower = err.to_ascii_lowercase();
const MARKER: &str = "maximum context length is ";
if let Some(idx) = lower.find(MARKER) {
if let Some(n) = first_number(&lower[idx + MARKER.len()..]) {
if (MIN_CTX..=MAX_CTX).contains(&n) {
return Some(n);
}
}
}
if let Some(idx) = lower.find("maximum") {
if let Some(n) = last_number(&lower[..idx]) {
if (MIN_CTX..=MAX_CTX).contains(&n) {
return Some(n);
}
}
}
for marker in ["context window of ", "context window is ", "context length of "] {
if let Some(idx) = lower.find(marker) {
if let Some(n) = first_number(&lower[idx + marker.len()..]) {
if (MIN_CTX..=MAX_CTX).contains(&n) {
return Some(n);
}
}
}
}
None
}
fn first_number(s: &str) -> Option<u32> {
let digits: String = s
.chars()
.skip_while(|c| !c.is_ascii_digit())
.take_while(|c| c.is_ascii_digit())
.collect();
digits.parse().ok()
}
fn last_number(s: &str) -> Option<u32> {
let chars: Vec<char> = s.chars().collect();
let mut end = chars.len();
while end > 0 && !chars[end - 1].is_ascii_digit() {
end -= 1;
}
let mut start = end;
while start > 0 && chars[start - 1].is_ascii_digit() {
start -= 1;
}
if end > start {
chars[start..end].iter().collect::<String>().parse().ok()
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn openai_format() {
let e = "This model's maximum context length is 128000 tokens. However, your messages resulted in 130000 tokens.";
assert_eq!(parse_context_limit(e), Some(128_000));
}
#[test]
fn anthropic_format() {
let e = "prompt is too long: 250000 tokens > 200000 maximum";
assert_eq!(parse_context_limit(e), Some(200_000));
}
#[test]
fn no_match() {
assert_eq!(parse_context_limit("rate limit exceeded"), None);
}
}