lazyBoy/crates/contracts/src/model.rs

98 lines
3.2 KiB
Rust
Raw Normal View History

2026-09-03 12:46:14 +00:00
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ModelProvider {
Xai,
Openai,
Anthropic,
Openrouter,
}
impl ModelProvider {
pub fn as_str(self) -> &'static str {
match self {
Self::Xai => "xai",
Self::Openai => "openai",
Self::Anthropic => "anthropic",
Self::Openrouter => "openrouter",
}
}
pub fn env_key_name(self) -> &'static str {
match self {
Self::Xai => "XAI_API_KEY",
Self::Openai => "OPENAI_API_KEY",
Self::Anthropic => "ANTHROPIC_API_KEY",
Self::Openrouter => "OPENROUTER_API_KEY",
}
}
/// First-party OpenAI-compatible base URL when we own the client mapping.
pub fn default_base_url(self) -> Option<&'static str> {
match self {
Self::Xai => Some("https://api.x.ai/v1"),
Self::Openai => Some("https://api.openai.com/v1"),
Self::Anthropic => None,
Self::Openrouter => Some("https://openrouter.ai/api/v1"),
}
}
}
impl std::str::FromStr for ModelProvider {
type Err = UnknownModelProvider;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"xai" => Ok(Self::Xai),
"openai" => Ok(Self::Openai),
"anthropic" => Ok(Self::Anthropic),
"openrouter" => Ok(Self::Openrouter),
other => Err(UnknownModelProvider(other.to_string())),
}
}
}
#[derive(Debug, Error, Clone, PartialEq, Eq)]
#[error("unknown model provider: {0}")]
pub struct UnknownModelProvider(pub String);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct ModelCapabilities {
pub vision: bool,
}
pub const DEFAULT_XAI_MODEL: &str = "grok-4.6";
pub fn default_model_id(provider: ModelProvider) -> Option<&'static str> {
match provider {
ModelProvider::Xai => Some(DEFAULT_XAI_MODEL),
ModelProvider::Openai | ModelProvider::Anthropic | ModelProvider::Openrouter => None,
}
}
/// Best-effort catalog. Unknown ids for a wired provider default to "no vision"
/// so we never shove screenshots at a model that may not accept them.
pub fn model_capabilities(provider: ModelProvider, model_id: &str) -> ModelCapabilities {
let vision = match provider {
ModelProvider::Xai => {
let id = model_id.to_ascii_lowercase();
id.starts_with("grok-4")
|| id.contains("vision")
|| id.starts_with("grok-2")
|| id.starts_with("grok-3")
}
ModelProvider::Openai => {
let id = model_id.to_ascii_lowercase();
id.contains("gpt-4o") || id.contains("gpt-5") || id.contains("vision")
}
ModelProvider::Anthropic => model_id.to_ascii_lowercase().contains("claude"),
ModelProvider::Openrouter => {
let id = model_id.to_ascii_lowercase();
id.contains("vision") || id.contains("gpt-4o") || id.contains("claude") || id.contains("grok")
}
};
ModelCapabilities { vision }
}