Skip to main content

embacle_server/runner/
mod.rs

1// ABOUTME: Runner management layer bridging REST handlers to embacle LlmProvider instances
2// ABOUTME: Provides factory creation, provider type parsing, and multiplex fan-out engine
3//
4// SPDX-License-Identifier: Apache-2.0
5// Copyright (c) 2026 dravr.ai
6
7pub mod factory;
8pub mod multiplex;
9
10use embacle::config::CliRunnerType;
11
12/// All provider types supported by embacle, in discovery priority order
13pub const ALL_PROVIDERS: &[CliRunnerType] = &[
14    CliRunnerType::ClaudeCode,
15    CliRunnerType::Copilot,
16    CliRunnerType::CursorAgent,
17    CliRunnerType::OpenCode,
18    CliRunnerType::GeminiCli,
19    CliRunnerType::CodexCli,
20    CliRunnerType::GooseCli,
21    CliRunnerType::ClineCli,
22    CliRunnerType::ContinueCli,
23];
24
25/// Parse a provider name string into a `CliRunnerType`
26///
27/// Accepts multiple naming conventions: `snake_case`, kebab-case, and
28/// short forms for flexible input.
29pub fn parse_runner_type(s: &str) -> Option<CliRunnerType> {
30    match s.to_lowercase().as_str() {
31        "claude_code" | "claude" | "claudecode" => Some(CliRunnerType::ClaudeCode),
32        "copilot" => Some(CliRunnerType::Copilot),
33        "cursor_agent" | "cursoragent" | "cursor-agent" => Some(CliRunnerType::CursorAgent),
34        "opencode" | "open_code" => Some(CliRunnerType::OpenCode),
35        "gemini" | "gemini_cli" | "geminicli" | "gemini-cli" => Some(CliRunnerType::GeminiCli),
36        "codex" | "codex_cli" | "codexcli" | "codex-cli" => Some(CliRunnerType::CodexCli),
37        "goose" | "goose_cli" | "goosecli" | "goose-cli" => Some(CliRunnerType::GooseCli),
38        "cline" | "cline_cli" | "clinecli" | "cline-cli" => Some(CliRunnerType::ClineCli),
39        "continue" | "continue_cli" | "continuecli" | "continue-cli" | "cn" => {
40            Some(CliRunnerType::ContinueCli)
41        }
42        _ => None,
43    }
44}
45
46/// Format the list of valid provider names for error messages
47pub const fn valid_provider_names() -> &'static str {
48    "claude_code, copilot, cursor_agent, opencode, gemini_cli, codex_cli, goose_cli, cline_cli, continue_cli"
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn parse_snake_case_variants() {
57        assert_eq!(
58            parse_runner_type("claude_code"),
59            Some(CliRunnerType::ClaudeCode)
60        );
61        assert_eq!(parse_runner_type("copilot"), Some(CliRunnerType::Copilot));
62        assert_eq!(
63            parse_runner_type("cursor_agent"),
64            Some(CliRunnerType::CursorAgent)
65        );
66        assert_eq!(parse_runner_type("opencode"), Some(CliRunnerType::OpenCode));
67        assert_eq!(
68            parse_runner_type("gemini_cli"),
69            Some(CliRunnerType::GeminiCli)
70        );
71        assert_eq!(
72            parse_runner_type("codex_cli"),
73            Some(CliRunnerType::CodexCli)
74        );
75        assert_eq!(
76            parse_runner_type("goose_cli"),
77            Some(CliRunnerType::GooseCli)
78        );
79        assert_eq!(
80            parse_runner_type("cline_cli"),
81            Some(CliRunnerType::ClineCli)
82        );
83        assert_eq!(
84            parse_runner_type("continue_cli"),
85            Some(CliRunnerType::ContinueCli)
86        );
87    }
88
89    #[test]
90    fn parse_short_forms() {
91        assert_eq!(parse_runner_type("claude"), Some(CliRunnerType::ClaudeCode));
92        assert_eq!(
93            parse_runner_type("cursor-agent"),
94            Some(CliRunnerType::CursorAgent)
95        );
96        assert_eq!(parse_runner_type("gemini"), Some(CliRunnerType::GeminiCli));
97        assert_eq!(parse_runner_type("codex"), Some(CliRunnerType::CodexCli));
98        assert_eq!(parse_runner_type("goose"), Some(CliRunnerType::GooseCli));
99        assert_eq!(parse_runner_type("cline"), Some(CliRunnerType::ClineCli));
100        assert_eq!(
101            parse_runner_type("continue"),
102            Some(CliRunnerType::ContinueCli)
103        );
104        assert_eq!(parse_runner_type("cn"), Some(CliRunnerType::ContinueCli));
105    }
106
107    #[test]
108    fn parse_case_insensitive() {
109        assert_eq!(parse_runner_type("COPILOT"), Some(CliRunnerType::Copilot));
110        assert_eq!(
111            parse_runner_type("Claude_Code"),
112            Some(CliRunnerType::ClaudeCode)
113        );
114    }
115
116    #[test]
117    fn parse_unknown_returns_none() {
118        assert_eq!(parse_runner_type("gpt4"), None);
119        assert_eq!(parse_runner_type(""), None);
120    }
121
122    #[test]
123    fn all_providers_has_nine_entries() {
124        assert_eq!(ALL_PROVIDERS.len(), 9);
125    }
126}