sac-cli 0.1.0

Terminal-based AI coding agent — fork of NAC with extended backend support and context management
Documentation
use super::*;

pub(super) fn default_model_for_backend(backend: BackendKind) -> String {
    match backend {
        BackendKind::DeepSeekChat => "deepseek-v4-pro".to_string(),
        BackendKind::OpenAiResponses => "gpt-5.5".to_string(),
        BackendKind::ChatGptCodexResponses => "gpt-5.5".to_string(),
        BackendKind::FireworksChat => "gpt-5.5".to_string(),
        BackendKind::Auto => unreachable!("auto backend does not have a default model"),
    }
}

pub(super) fn default_reasoning_effort(backend: BackendKind) -> Option<ReasoningEffort> {
    match backend {
        BackendKind::OpenAiResponses | BackendKind::ChatGptCodexResponses => {
            Some(ReasoningEffort::Xhigh)
        }
        BackendKind::DeepSeekChat => None,
        BackendKind::FireworksChat => None,
        BackendKind::Auto => None,
    }
}

pub(super) fn default_base_url_for_backend_hint(backend: BackendKind) -> &'static str {
    match backend {
        BackendKind::DeepSeekChat => "https://api.deepseek.com",
        BackendKind::ChatGptCodexResponses => "https://chatgpt.com/backend-api",
        BackendKind::Auto | BackendKind::FireworksChat | BackendKind::OpenAiResponses => {
            "https://api.openai.com/v1"
        }
    }
}

pub(super) fn api_key_for_backend(
    backend: BackendKind,
    configured_env: Option<&str>,
    configured_api_key: Option<&str>,
) -> Result<String> {
    match backend {
        BackendKind::ChatGptCodexResponses => Ok(String::new()),
        BackendKind::Auto
        | BackendKind::DeepSeekChat
        | BackendKind::FireworksChat
        | BackendKind::OpenAiResponses => {
            if let Ok(api_key) = std::env::var("OPENAI_API_KEY") {
                return Ok(api_key);
            }
            if let Some(env_name) = configured_env.filter(|name| *name != "OPENAI_API_KEY") {
                if let Ok(api_key) = std::env::var(env_name) {
                    return Ok(api_key);
                }
            }
            if let Some(api_key) = configured_api_key.filter(|value| !value.trim().is_empty()) {
                return Ok(api_key.to_string());
            }
            if let Some(env_name) = configured_env.filter(|name| *name != "OPENAI_API_KEY") {
                return Err(anyhow!(
                    "OPENAI_API_KEY environment variable is not set, configured api_key_env '{}' is not set, and no config model.api_key is present",
                    env_name
                ));
            }
            Err(anyhow!("OPENAI_API_KEY environment variable is not set"))
        }
    }
}

pub fn detect_backend(base_url: &str) -> Result<BackendKind> {
    let parsed = Url::parse(base_url)
        .map_err(|error| anyhow!("failed to parse OPENAI_BASE_URL '{}': {}", base_url, error))?;
    let host = parsed
        .host_str()
        .ok_or_else(|| anyhow!("OPENAI_BASE_URL '{}' does not include a host", base_url))?;

    if host.contains("fireworks.ai") {
        return Ok(BackendKind::FireworksChat);
    }
    if host == "api.deepseek.com" {
        return Ok(BackendKind::DeepSeekChat);
    }
    if host == "api.openai.com" {
        return Ok(BackendKind::OpenAiResponses);
    }
    if host == "chatgpt.com" && parsed.path().contains("/backend-api") {
        return Ok(BackendKind::ChatGptCodexResponses);
    }

    Err(anyhow!(
        "could not infer backend from '{}'; pass --backend deepseek-chat, --backend fireworks-chat, --backend openai-responses, or --backend chatgpt-codex-responses",
        base_url
    ))
}