use crate::{Result, config::Provider, openai::response::ModelList};
use std::collections::HashSet;
pub const OPENCLAW_CODEX_MODELS: &[&str] = &[
"gpt-5.1",
"gpt-5.1-codex-max",
"gpt-5.1-codex-mini",
"gpt-5.2",
"gpt-5.2-codex",
"gpt-5.3-codex",
"gpt-5.3-codex-spark",
"gpt-5.4",
"gpt-5.4-mini",
"gpt-5.5",
];
pub const GROK_MODELS: &[&str] = &["grok-4.3", "grok-4.3-fast", "grok-4"];
#[must_use]
pub fn resolve_model_ids() -> Vec<String> {
resolve_model_ids_for_provider(Provider::Codex)
}
#[must_use]
pub fn resolve_model_ids_for_provider(provider: Provider) -> Vec<String> {
let ids = match provider {
Provider::Codex => OPENCLAW_CODEX_MODELS,
Provider::Grok => GROK_MODELS,
};
normalize_model_ids(ids.iter().map(ToString::to_string))
}
pub fn resolve_model_list() -> Result<ModelList> {
resolve_model_list_for_provider(Provider::Codex)
}
pub fn resolve_model_list_for_provider(provider: Provider) -> Result<ModelList> {
Ok(ModelList::from_ids(resolve_model_ids_for_provider(
provider,
)))
}
#[must_use]
pub fn provider_for_model(model: &str) -> Provider {
let normalized = model.strip_prefix("openai-codex/").unwrap_or(model);
let normalized = normalized
.strip_prefix("xai/")
.or_else(|| normalized.strip_prefix("grok/"))
.unwrap_or(normalized);
if normalized.starts_with("grok-") {
Provider::Grok
} else {
Provider::Codex
}
}
pub fn resolve_model_list_for_providers(providers: &[Provider]) -> Result<ModelList> {
let ids = providers
.iter()
.copied()
.flat_map(resolve_model_ids_for_provider)
.collect::<Vec<_>>();
Ok(ModelList::from_ids(normalize_model_ids(ids)))
}
fn normalize_model_ids(ids: impl IntoIterator<Item = String>) -> Vec<String> {
let mut seen = HashSet::new();
ids.into_iter()
.map(|id| id.trim().to_owned())
.filter(|id| !id.is_empty())
.filter(|id| seen.insert(id.clone()))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_to_openclaw_codex_models() {
let ids = resolve_model_ids();
let expected = OPENCLAW_CODEX_MODELS
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>();
assert_eq!(ids, expected);
}
#[test]
fn defaults_include_gpt_55_models() {
let ids = resolve_model_ids();
assert!(ids.iter().any(|id| id == "gpt-5.5"));
assert!(!ids.iter().any(|id| id == "gpt-5.5-mini"));
}
#[test]
fn grok_defaults_include_grok_models() {
let ids = resolve_model_ids_for_provider(Provider::Grok);
assert!(ids.iter().any(|id| id == "grok-4.3"));
assert!(!ids.iter().any(|id| id == "gpt-5.5"));
}
#[test]
fn provider_detection_handles_prefixed_grok_models() {
assert_eq!(provider_for_model("grok-4.3"), Provider::Grok);
assert_eq!(provider_for_model("xai/grok-4.3"), Provider::Grok);
assert_eq!(provider_for_model("grok/grok-4.3"), Provider::Grok);
assert_eq!(provider_for_model("openai-codex/grok-4.3"), Provider::Grok);
assert_eq!(provider_for_model("openai-codex/gpt-5.5"), Provider::Codex);
}
}