#![cfg(feature = "cure-network")]
use std::collections::HashMap;
use std::time::Duration;
use serde::Deserialize;
use super::types::{CuredModel, CureError};
const OPENROUTER_MODELS_URL: &str = "https://openrouter.ai/api/v1/models";
const REQUEST_TIMEOUT_SECS: u64 = 10;
fn normalize_model_id(raw: &str) -> String {
let model_part = raw.split('/').next_back().unwrap_or(raw);
replace_version_dots(model_part)
}
fn replace_version_dots(s: &str) -> String {
let bytes = s.as_bytes();
let mut out = String::with_capacity(s.len());
let mut i = 0;
while i < bytes.len() {
if i + 2 <= bytes.len().saturating_sub(1)
&& bytes[i].is_ascii_digit()
&& bytes[i + 1] == b'.'
&& bytes[i + 2].is_ascii_digit()
{
out.push(bytes[i] as char);
out.push('-');
i += 2; } else {
out.push(bytes[i] as char);
i += 1;
}
}
out
}
#[derive(Debug, Deserialize)]
struct OpenRouterResponse {
data: Vec<OpenRouterModel>,
}
#[derive(Debug, Deserialize)]
struct OpenRouterModel {
id: String,
name: String,
context_length: u64,
}
fn openrouter_provider_to_tool_id(provider: &str) -> Option<&'static str> {
match provider {
"anthropic" => Some("claude_code"),
"openai" => Some("codex"),
"google" => Some("gemini"),
_ => None,
}
}
pub(crate) async fn fetch_openrouter_models() -> Result<HashMap<String, Vec<CuredModel>>, CureError> {
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(REQUEST_TIMEOUT_SECS))
.build()?;
let response: OpenRouterResponse = client
.get(OPENROUTER_MODELS_URL)
.send()
.await?
.json()
.await?;
let mut acc: HashMap<String, HashMap<String, CuredModel>> = HashMap::new();
for model in response.data {
let Some((provider, _)) = model.id.split_once('/') else {
continue;
};
let Some(tool_id) = openrouter_provider_to_tool_id(provider) else {
continue;
};
let normalized_id = normalize_model_id(&model.id);
acc.entry(tool_id.to_string())
.or_default()
.insert(
normalized_id.clone(),
CuredModel {
id: normalized_id,
display_name: model.name,
context_window: if model.context_length > 0 {
Some(model.context_length)
} else {
None
},
max_output: None,
},
);
}
Ok(acc
.into_iter()
.map(|(tool_id, models_map)| (tool_id, models_map.into_values().collect()))
.collect())
}