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