Skip to main content

ralph_workflow/cli/
providers.rs

1//! Provider listing and information display.
2//!
3//! Contains functions for displaying `OpenCode` provider information.
4
5use crate::agents::OpenCodeProviderType;
6use crate::logger::Colors;
7
8/// Provider category for display organization
9#[derive(Debug, Clone, Copy)]
10struct ProviderCategory {
11    name: &'static str,
12    providers: &'static [(OpenCodeProviderType, &'static str)],
13}
14
15/// Provider categories with their providers and example aliases
16const PROVIDER_CATEGORIES: &[ProviderCategory] = &[
17    ProviderCategory {
18        name: "OPENCODE GATEWAY",
19        providers: &[(OpenCodeProviderType::OpenCodeZen, "opencode-zen-glm")],
20    },
21    ProviderCategory {
22        name: "CHINESE AI PROVIDERS",
23        providers: &[
24            (OpenCodeProviderType::ZaiDirect, "opencode-zai-glm"),
25            (
26                OpenCodeProviderType::ZaiCodingPlan,
27                "opencode-zai-glm-codingplan",
28            ),
29            (OpenCodeProviderType::Moonshot, "opencode-moonshot"),
30            (OpenCodeProviderType::MiniMax, "opencode-minimax"),
31        ],
32    },
33    ProviderCategory {
34        name: "MAJOR CLOUD PROVIDERS",
35        providers: &[
36            (OpenCodeProviderType::Anthropic, "opencode-direct-claude"),
37            (OpenCodeProviderType::OpenAI, "opencode-openai"),
38            (OpenCodeProviderType::Google, "opencode-google"),
39            (OpenCodeProviderType::GoogleVertex, "opencode-vertex"),
40            (OpenCodeProviderType::AmazonBedrock, "opencode-bedrock"),
41            (OpenCodeProviderType::AzureOpenAI, "opencode-azure"),
42            (OpenCodeProviderType::GithubCopilot, "opencode-copilot"),
43        ],
44    },
45    ProviderCategory {
46        name: "FAST INFERENCE PROVIDERS",
47        providers: &[
48            (OpenCodeProviderType::Groq, "opencode-groq"),
49            (OpenCodeProviderType::Together, "opencode-together"),
50            (OpenCodeProviderType::Fireworks, "opencode-fireworks"),
51            (OpenCodeProviderType::Cerebras, "opencode-cerebras"),
52            (OpenCodeProviderType::SambaNova, "opencode-sambanova"),
53            (OpenCodeProviderType::DeepInfra, "opencode-deepinfra"),
54        ],
55    },
56    ProviderCategory {
57        name: "GATEWAY PROVIDERS",
58        providers: &[
59            (OpenCodeProviderType::OpenRouter, "opencode-openrouter"),
60            (OpenCodeProviderType::Cloudflare, "opencode-cloudflare"),
61        ],
62    },
63    ProviderCategory {
64        name: "SPECIALIZED PROVIDERS",
65        providers: &[
66            (OpenCodeProviderType::DeepSeek, "opencode-deepseek"),
67            (OpenCodeProviderType::Xai, "opencode-xai"),
68            (OpenCodeProviderType::Mistral, "opencode-mistral"),
69            (OpenCodeProviderType::Cohere, "opencode-cohere"),
70            (OpenCodeProviderType::Perplexity, "opencode-perplexity"),
71            (OpenCodeProviderType::AI21, "opencode-ai21"),
72            (OpenCodeProviderType::VeniceAI, "opencode-venice"),
73        ],
74    },
75    ProviderCategory {
76        name: "OPEN-SOURCE MODEL PROVIDERS",
77        providers: &[
78            (OpenCodeProviderType::HuggingFace, "opencode-huggingface"),
79            (OpenCodeProviderType::Replicate, "opencode-replicate"),
80        ],
81    },
82    ProviderCategory {
83        name: "CLOUD PLATFORM PROVIDERS",
84        providers: &[
85            (OpenCodeProviderType::Baseten, "opencode-baseten"),
86            (OpenCodeProviderType::Cortecs, "opencode-cortecs"),
87            (OpenCodeProviderType::Scaleway, "opencode-scaleway"),
88            (OpenCodeProviderType::OVHcloud, "opencode-ovhcloud"),
89            (OpenCodeProviderType::IONet, "opencode-ionet"),
90            (OpenCodeProviderType::Nebius, "opencode-nebius"),
91        ],
92    },
93    ProviderCategory {
94        name: "AI GATEWAY PROVIDERS",
95        providers: &[
96            (OpenCodeProviderType::Vercel, "opencode-vercel"),
97            (OpenCodeProviderType::Helicone, "opencode-helicone"),
98            (OpenCodeProviderType::ZenMux, "opencode-zenmux"),
99        ],
100    },
101    ProviderCategory {
102        name: "ENTERPRISE PROVIDERS",
103        providers: &[
104            (OpenCodeProviderType::SapAICore, "opencode-sap"),
105            (
106                OpenCodeProviderType::AzureCognitiveServices,
107                "opencode-azure-cognitive",
108            ),
109        ],
110    },
111    ProviderCategory {
112        name: "LOCAL PROVIDERS",
113        providers: &[
114            (OpenCodeProviderType::Ollama, "opencode-ollama"),
115            (OpenCodeProviderType::LMStudio, "opencode-lmstudio"),
116            (OpenCodeProviderType::OllamaCloud, "opencode-ollama-cloud"),
117            (OpenCodeProviderType::LlamaCpp, "opencode-llamacpp"),
118        ],
119    },
120    ProviderCategory {
121        name: "CUSTOM",
122        providers: &[(OpenCodeProviderType::Custom, "(custom)")],
123    },
124];
125
126/// Helper function to print provider information for --list-providers.
127pub fn print_provider_info(colors: Colors, provider: OpenCodeProviderType, agent_alias: &str) {
128    let examples = provider.example_models();
129    let example_str = if examples.is_empty() {
130        String::new()
131    } else {
132        format!(" (e.g., {})", examples[0])
133    };
134
135    println!("{}{}{}", colors.bold(), provider.name(), colors.reset());
136    println!("  Prefix: {}{}", provider.prefix(), example_str);
137    println!("  Auth: {}", provider.auth_command());
138    println!("  Agent: {agent_alias}");
139}
140
141/// Print a provider category with all its providers
142fn print_category(colors: Colors, category: &ProviderCategory) {
143    println!(
144        "{}═══ {} ═══{}",
145        colors.bold(),
146        category.name,
147        colors.reset()
148    );
149    for (provider, alias) in category.providers {
150        print_provider_info(colors, *provider, alias);
151    }
152    println!();
153}
154
155/// Print important notes about providers
156fn print_notes(colors: Colors) {
157    println!("{}═══ IMPORTANT NOTES ═══{}", colors.bold(), colors.reset());
158    println!(
159        "• OpenCode Zen (opencode/*) and Z.AI Direct (zai/* or zhipuai/*) are SEPARATE endpoints!"
160    );
161    println!("  - opencode/* routes through OpenCode's Zen gateway at opencode.ai");
162    println!("  - zai/* or zhipuai/* connects directly to Z.AI's API at api.z.ai");
163    println!("  - Z.AI Coding Plan is an auth tier; model prefix remains zai/* or zhipuai/*");
164    println!("• Cloud providers (Vertex, Bedrock, Azure, SAP) require additional configuration");
165    println!(
166        "• Local providers (Ollama, LM Studio, llama.cpp) run on your hardware - no API key needed"
167    );
168    println!("• Use clear naming: opencode-zen-*, opencode-zai-*, opencode-direct-* aliases");
169    println!();
170}
171
172/// Handle --list-providers command.
173///
174/// Displays a categorized list of all `OpenCode` provider types with their
175/// model prefixes, authentication commands, and example agent aliases.
176pub fn handle_list_providers(colors: Colors) {
177    println!("{}OpenCode Provider Types{}", colors.bold(), colors.reset());
178    println!();
179    println!("Ralph includes built-in guidance for major OpenCode provider prefixes (plus a custom fallback).");
180    println!("OpenCode may support additional providers; consult OpenCode docs for the full set.");
181    println!();
182
183    for category in PROVIDER_CATEGORIES {
184        print_category(colors, category);
185    }
186
187    print_notes(colors);
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    #[test]
195    fn test_provider_categories_count() {
196        // Verify we have all expected categories
197        assert_eq!(PROVIDER_CATEGORIES.len(), 12);
198    }
199
200    #[test]
201    fn test_category_names() {
202        let expected_names = &[
203            "OPENCODE GATEWAY",
204            "CHINESE AI PROVIDERS",
205            "MAJOR CLOUD PROVIDERS",
206            "FAST INFERENCE PROVIDERS",
207            "GATEWAY PROVIDERS",
208            "SPECIALIZED PROVIDERS",
209            "OPEN-SOURCE MODEL PROVIDERS",
210            "CLOUD PLATFORM PROVIDERS",
211            "AI GATEWAY PROVIDERS",
212            "ENTERPRISE PROVIDERS",
213            "LOCAL PROVIDERS",
214            "CUSTOM",
215        ];
216        let actual_names: Vec<_> = PROVIDER_CATEGORIES.iter().map(|c| c.name).collect();
217        assert_eq!(actual_names, expected_names);
218    }
219
220    #[test]
221    fn test_no_empty_categories() {
222        for category in PROVIDER_CATEGORIES {
223            assert!(
224                !category.providers.is_empty(),
225                "Category '{}' is empty",
226                category.name
227            );
228        }
229    }
230
231    #[test]
232    fn test_all_providers_have_aliases() {
233        for category in PROVIDER_CATEGORIES {
234            for (provider, alias) in category.providers {
235                assert!(!alias.is_empty(), "Provider {provider:?} has empty alias");
236            }
237        }
238    }
239}