#![cfg_attr(docsrs, feature(doc_cfg))]
pub use nucel_agent_core::{
AgentCapabilities, AgentCost, AgentError, AgentExecutor, AgentResponse, AgentSession,
AvailabilityStatus, CachePoint, EventStream, ExecutorType, HookConfig, HookHandler,
MessageEvent, PermissionMode, Result, SessionImpl, SessionMetadata, SpawnConfig,
};
pub use nucel_agent_claude_code::ClaudeCodeExecutor;
pub use nucel_agent_codex::CodexExecutor;
pub use nucel_agent_opencode::OpencodeExecutor;
pub fn build_executor(
provider: &str,
api_key_or_url: Option<String>,
) -> Option<Box<dyn AgentExecutor>> {
match provider {
"claude-code" | "claude_code" | "claudecode" => Some(Box::new(ClaudeCodeExecutor::new())),
"codex" => Some(Box::new(CodexExecutor::new())),
"opencode" => {
let mut exec = OpencodeExecutor::new();
if let Some(url) = api_key_or_url {
exec = OpencodeExecutor::with_base_url(url);
}
Some(Box::new(exec))
}
_ => None,
}
}
pub fn available_providers() -> &'static [&'static str] {
&["claude-code", "codex", "opencode"]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_claude_code_executor() {
let exec = build_executor("claude-code", None).unwrap();
assert_eq!(exec.executor_type(), ExecutorType::ClaudeCode);
}
#[test]
fn build_codex_executor() {
let exec = build_executor("codex", None).unwrap();
assert_eq!(exec.executor_type(), ExecutorType::Codex);
}
#[test]
fn build_opencode_executor() {
let exec = build_executor("opencode", None).unwrap();
assert_eq!(exec.executor_type(), ExecutorType::OpenCode);
}
#[test]
fn build_opencode_with_url() {
let exec = build_executor("opencode", Some("http://my-server:8080".into())).unwrap();
assert_eq!(exec.executor_type(), ExecutorType::OpenCode);
}
#[test]
fn unknown_provider_returns_none() {
assert!(build_executor("gpt-4", None).is_none());
}
#[test]
fn claude_code_aliases_work() {
assert!(build_executor("claude_code", None).is_some());
assert!(build_executor("claudecode", None).is_some());
}
#[test]
fn available_providers_list() {
let providers = available_providers();
assert_eq!(providers.len(), 3);
assert!(providers.contains(&"claude-code"));
assert!(providers.contains(&"codex"));
assert!(providers.contains(&"opencode"));
}
#[test]
fn build_executor_empty_string_returns_none() {
assert!(build_executor("", None).is_none());
}
#[test]
fn build_executor_case_sensitive() {
assert!(build_executor("Claude-Code", None).is_none());
assert!(build_executor("CODEX", None).is_none());
assert!(build_executor("OpenCode", None).is_none());
}
#[test]
fn all_executors_have_capabilities() {
for provider in available_providers() {
let exec = build_executor(provider, None).unwrap();
let caps = exec.capabilities();
assert!(caps.token_usage, "{provider} should support token_usage");
assert!(caps.autonomous_mode, "{provider} should support autonomous_mode");
}
}
#[test]
fn all_executors_report_availability() {
for provider in available_providers() {
let exec = build_executor(provider, None).unwrap();
let status = exec.availability();
if !status.available {
assert!(status.reason.is_some(), "{provider} unavailable but no reason");
}
}
}
#[test]
fn claude_code_api_key_ignored_by_build_executor() {
let exec = build_executor("claude-code", Some("sk-test".into())).unwrap();
assert_eq!(exec.executor_type(), ExecutorType::ClaudeCode);
}
#[test]
fn codex_api_key_ignored_by_build_executor() {
let exec = build_executor("codex", Some("sk-test".into())).unwrap();
assert_eq!(exec.executor_type(), ExecutorType::Codex);
}
}