Skip to main content

sac/model/
backend.rs

1use super::*;
2
3pub(super) fn default_model_for_backend(backend: BackendKind) -> String {
4    match backend {
5        BackendKind::DeepSeekChat => "deepseek-v4-pro".to_string(),
6        BackendKind::OpenAiResponses => "gpt-5.5".to_string(),
7        BackendKind::ChatGptCodexResponses => "gpt-5.5".to_string(),
8        BackendKind::FireworksChat => "gpt-5.5".to_string(),
9        BackendKind::Auto => unreachable!("auto backend does not have a default model"),
10    }
11}
12
13pub(super) fn default_reasoning_effort(backend: BackendKind) -> Option<ReasoningEffort> {
14    match backend {
15        BackendKind::OpenAiResponses | BackendKind::ChatGptCodexResponses => {
16            Some(ReasoningEffort::Xhigh)
17        }
18        BackendKind::DeepSeekChat => None,
19        BackendKind::FireworksChat => None,
20        BackendKind::Auto => None,
21    }
22}
23
24pub(super) fn default_base_url_for_backend_hint(backend: BackendKind) -> &'static str {
25    match backend {
26        BackendKind::DeepSeekChat => "https://api.deepseek.com",
27        BackendKind::ChatGptCodexResponses => "https://chatgpt.com/backend-api",
28        BackendKind::Auto | BackendKind::FireworksChat | BackendKind::OpenAiResponses => {
29            "https://api.openai.com/v1"
30        }
31    }
32}
33
34pub(super) fn api_key_for_backend(
35    backend: BackendKind,
36    configured_env: Option<&str>,
37    configured_api_key: Option<&str>,
38) -> Result<String> {
39    match backend {
40        BackendKind::ChatGptCodexResponses => Ok(String::new()),
41        BackendKind::Auto
42        | BackendKind::DeepSeekChat
43        | BackendKind::FireworksChat
44        | BackendKind::OpenAiResponses => {
45            if let Ok(api_key) = std::env::var("OPENAI_API_KEY") {
46                return Ok(api_key);
47            }
48            if let Some(env_name) = configured_env.filter(|name| *name != "OPENAI_API_KEY") {
49                if let Ok(api_key) = std::env::var(env_name) {
50                    return Ok(api_key);
51                }
52            }
53            if let Some(api_key) = configured_api_key.filter(|value| !value.trim().is_empty()) {
54                return Ok(api_key.to_string());
55            }
56            if let Some(env_name) = configured_env.filter(|name| *name != "OPENAI_API_KEY") {
57                return Err(anyhow!(
58                    "OPENAI_API_KEY environment variable is not set, configured api_key_env '{}' is not set, and no config model.api_key is present",
59                    env_name
60                ));
61            }
62            Err(anyhow!("OPENAI_API_KEY environment variable is not set"))
63        }
64    }
65}
66
67pub fn detect_backend(base_url: &str) -> Result<BackendKind> {
68    let parsed = Url::parse(base_url)
69        .map_err(|error| anyhow!("failed to parse OPENAI_BASE_URL '{}': {}", base_url, error))?;
70    let host = parsed
71        .host_str()
72        .ok_or_else(|| anyhow!("OPENAI_BASE_URL '{}' does not include a host", base_url))?;
73
74    if host.contains("fireworks.ai") {
75        return Ok(BackendKind::FireworksChat);
76    }
77    if host == "api.deepseek.com" {
78        return Ok(BackendKind::DeepSeekChat);
79    }
80    if host == "api.openai.com" {
81        return Ok(BackendKind::OpenAiResponses);
82    }
83    if host == "chatgpt.com" && parsed.path().contains("/backend-api") {
84        return Ok(BackendKind::ChatGptCodexResponses);
85    }
86
87    Err(anyhow!(
88        "could not infer backend from '{}'; pass --backend deepseek-chat, --backend fireworks-chat, --backend openai-responses, or --backend chatgpt-codex-responses",
89        base_url
90    ))
91}