Skip to main content

ralph_workflow/agents/providers/
validation.rs

1//! Validation Functions
2//!
3//! Model flag validation and authentication failure advice.
4
5use super::detection::strip_model_flag_prefix;
6use super::types::OpenCodeProviderType;
7
8/// Validate a model flag and return provider-specific warnings if any issues detected.
9///
10/// Returns a vector of warning messages (empty if no issues).
11#[must_use]
12pub fn validate_model_flag(model_flag: &str) -> Vec<String> {
13    let mut warnings = Vec::new();
14
15    let model = strip_model_flag_prefix(model_flag);
16    if model.is_empty() {
17        return warnings;
18    }
19
20    // Ensure model flag has provider prefix
21    if !model.contains('/') {
22        warnings.push(format!(
23            "Model '{model}' has no provider prefix. Expected format: 'provider/model' (e.g., 'opencode/glm-4.7-free')"
24        ));
25        return warnings;
26    }
27
28    let provider_type = OpenCodeProviderType::from_model_flag(model);
29
30    // Warn about Z.AI vs Zen confusion
31    if provider_type == OpenCodeProviderType::OpenCodeZen && model.to_lowercase().contains("zai") {
32        warnings.push(
33            "Model flag uses 'opencode/' prefix but contains 'zai'. \
34             For Z.AI Direct access, use 'zai/' prefix instead."
35                .to_string(),
36        );
37    }
38
39    // Warn about providers requiring cloud configuration
40    if provider_type.requires_cloud() {
41        warnings.push(format!(
42            "{} provider requires cloud configuration. {}",
43            provider_type.name(),
44            provider_type.auth_command()
45        ));
46    }
47
48    // Warn about custom/unknown providers
49    if provider_type == OpenCodeProviderType::Custom {
50        let prefix = model.split('/').next().unwrap_or("");
51        warnings.push(format!(
52            "Unknown provider prefix '{prefix}'. This may work if OpenCode supports it. \
53             Run 'ralph --list-providers' to see known providers."
54        ));
55    }
56
57    // Info about local providers
58    if provider_type.is_local() {
59        warnings.push(format!(
60            "{} is a local provider. {}",
61            provider_type.name(),
62            provider_type.auth_command()
63        ));
64    }
65
66    warnings
67}
68
69/// Get provider-specific authentication failure advice based on model flag.
70#[must_use]
71pub fn auth_failure_advice(model_flag: Option<&str>) -> String {
72    match model_flag {
73        Some(flag) => {
74            let model = strip_model_flag_prefix(flag);
75            let prefix = model.split('/').next().unwrap_or("").to_lowercase();
76            if matches!(prefix.as_str(), "zai" | "zhipuai") {
77                return "Authentication failed for Z.AI provider. Run: opencode auth login -> select 'Z.AI' or 'Z.AI Coding Plan'".to_string();
78            }
79            let provider = OpenCodeProviderType::from_model_flag(flag);
80            format!(
81                "Authentication failed for {} provider. Run: {}",
82                provider.name(),
83                provider.auth_command()
84            )
85        }
86        None => "Check API key or run 'opencode auth login' to authenticate.".to_string(),
87    }
88}