embacle_server/
provider_resolver.rs1use embacle::config::CliRunnerType;
8
9use crate::runner::parse_runner_type;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct ResolvedProvider {
14 pub runner_type: CliRunnerType,
16 pub model: Option<String>,
18}
19
20pub fn resolve_model(model_str: &str, default_provider: CliRunnerType) -> ResolvedProvider {
28 if let Some((prefix, model)) = model_str.split_once(':') {
29 if let Some(runner_type) = parse_runner_type(prefix) {
30 return ResolvedProvider {
31 runner_type,
32 model: if model.is_empty() {
33 None
34 } else {
35 Some(model.to_owned())
36 },
37 };
38 }
39 ResolvedProvider {
41 runner_type: default_provider,
42 model: Some(model_str.to_owned()),
43 }
44 } else if let Some(runner_type) = parse_runner_type(model_str) {
45 ResolvedProvider {
47 runner_type,
48 model: None,
49 }
50 } else {
51 ResolvedProvider {
53 runner_type: default_provider,
54 model: Some(model_str.to_owned()),
55 }
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62
63 #[test]
64 fn resolve_provider_with_model() {
65 let result = resolve_model("copilot:gpt-4o", CliRunnerType::ClaudeCode);
66 assert_eq!(result.runner_type, CliRunnerType::Copilot);
67 assert_eq!(result.model.as_deref(), Some("gpt-4o"));
68 }
69
70 #[test]
71 fn resolve_claude_with_model() {
72 let result = resolve_model("claude:opus", CliRunnerType::Copilot);
73 assert_eq!(result.runner_type, CliRunnerType::ClaudeCode);
74 assert_eq!(result.model.as_deref(), Some("opus"));
75 }
76
77 #[test]
78 fn resolve_provider_only() {
79 let result = resolve_model("copilot", CliRunnerType::ClaudeCode);
80 assert_eq!(result.runner_type, CliRunnerType::Copilot);
81 assert!(result.model.is_none());
82 }
83
84 #[test]
85 fn resolve_bare_model_uses_default() {
86 let result = resolve_model("gpt-4o", CliRunnerType::Copilot);
87 assert_eq!(result.runner_type, CliRunnerType::Copilot);
88 assert_eq!(result.model.as_deref(), Some("gpt-4o"));
89 }
90
91 #[test]
92 fn resolve_provider_with_empty_model() {
93 let result = resolve_model("copilot:", CliRunnerType::ClaudeCode);
94 assert_eq!(result.runner_type, CliRunnerType::Copilot);
95 assert!(result.model.is_none());
96 }
97
98 #[test]
99 fn resolve_unknown_prefix_as_bare_model() {
100 let result = resolve_model("unknown:something", CliRunnerType::Copilot);
101 assert_eq!(result.runner_type, CliRunnerType::Copilot);
102 assert_eq!(result.model.as_deref(), Some("unknown:something"));
103 }
104
105 #[test]
106 fn resolve_case_insensitive_provider() {
107 let result = resolve_model("CLAUDE:opus", CliRunnerType::Copilot);
108 assert_eq!(result.runner_type, CliRunnerType::ClaudeCode);
109 assert_eq!(result.model.as_deref(), Some("opus"));
110 }
111
112 #[test]
113 fn resolve_cursor_agent_variants() {
114 for prefix in &["cursor_agent", "cursor-agent", "cursoragent"] {
115 let model_str = format!("{prefix}:model");
116 let result = resolve_model(&model_str, CliRunnerType::Copilot);
117 assert_eq!(result.runner_type, CliRunnerType::CursorAgent);
118 assert_eq!(result.model.as_deref(), Some("model"));
119 }
120 }
121
122 #[test]
123 fn resolve_opencode_variants() {
124 let result = resolve_model("opencode:latest", CliRunnerType::Copilot);
125 assert_eq!(result.runner_type, CliRunnerType::OpenCode);
126 assert_eq!(result.model.as_deref(), Some("latest"));
127
128 let result = resolve_model("open_code:latest", CliRunnerType::Copilot);
129 assert_eq!(result.runner_type, CliRunnerType::OpenCode);
130 assert_eq!(result.model.as_deref(), Some("latest"));
131 }
132}