1use crate::agents::OpenCodeProviderType;
6use crate::logger::Colors;
7
8#[derive(Debug, Clone, Copy)]
10struct ProviderCategory {
11 name: &'static str,
12 providers: &'static [(OpenCodeProviderType, &'static str)],
13}
14
15const 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
126pub 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
141fn 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
155fn 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
172pub 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 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}