use agent_common::{AgentCliAdapter, AgentCliCommand};
use claude::{ClaudeWrapper, CursorWrapper};
use cli_common::Agent;
use cline::ClineWrapper;
use codex::CodexWrapper;
use copilot::CopilotWrapper;
use gemini::GeminiWrapper;
use grok::GrokWrapper;
use junie::JunieWrapper;
use xai::XaiWrapper;
pub const PROMPT_STDIN_BYTE_THRESHOLD: usize = 64 * 1024;
pub struct NativeRunCommand {
pub command: AgentCliCommand,
pub prompt_transport: PromptTransport,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PromptTransport {
Argv,
Stdin,
}
pub fn native_base_command(agent: Agent, prompt: &str) -> AgentCliCommand {
match agent {
Agent::Claude => AgentCliCommand {
binary: ClaudeWrapper.binary().to_string(),
args: ClaudeWrapper.caretta_native_run_argv(prompt),
},
Agent::Cursor => AgentCliCommand {
binary: CursorWrapper.binary().to_string(),
args: CursorWrapper.caretta_native_run_argv(prompt),
},
Agent::Junie => AgentCliCommand {
binary: JunieWrapper.binary().to_string(),
args: JunieWrapper.caretta_native_run_argv(prompt),
},
Agent::Copilot => AgentCliCommand {
binary: CopilotWrapper.binary().to_string(),
args: CopilotWrapper.caretta_native_run_argv(prompt),
},
Agent::Codex => AgentCliCommand {
binary: CodexWrapper.binary().to_string(),
args: CodexWrapper.caretta_native_run_argv(prompt),
},
Agent::Gemini => AgentCliCommand {
binary: GeminiWrapper.binary().to_string(),
args: GeminiWrapper.caretta_native_run_argv(prompt),
},
Agent::Grok => AgentCliCommand {
binary: GrokWrapper.binary().to_string(),
args: GrokWrapper.caretta_native_run_argv(prompt),
},
Agent::Xai => AgentCliCommand {
binary: XaiWrapper.binary().to_string(),
args: XaiWrapper.caretta_native_run_argv(prompt),
},
Agent::Cline => AgentCliCommand {
binary: ClineWrapper.binary().to_string(),
args: ClineWrapper.caretta_native_run_argv(prompt),
},
}
}
pub fn caretta_native_command(
agent: Agent,
prompt: &str,
extra_args: &[String],
) -> AgentCliCommand {
let mut cmd = native_base_command(agent, prompt);
cmd.args.extend_from_slice(extra_args);
cmd
}
pub fn caretta_native_command_with_prompt_transport(
agent: Agent,
prompt: &str,
extra_args: &[String],
) -> NativeRunCommand {
let use_stdin = prompt.len() > PROMPT_STDIN_BYTE_THRESHOLD;
let mut command = if use_stdin {
native_stdin_command(agent).unwrap_or_else(|| native_base_command(agent, prompt))
} else {
native_base_command(agent, prompt)
};
command.args.extend_from_slice(extra_args);
NativeRunCommand {
command,
prompt_transport: if use_stdin && supports_stdin_prompt(agent) {
PromptTransport::Stdin
} else {
PromptTransport::Argv
},
}
}
fn supports_stdin_prompt(agent: Agent) -> bool {
matches!(
agent,
Agent::Claude | Agent::Cursor | Agent::Junie | Agent::Codex
)
}
fn native_stdin_command(agent: Agent) -> Option<AgentCliCommand> {
match agent {
Agent::Claude => Some(AgentCliCommand {
binary: ClaudeWrapper.binary().to_string(),
args: claude_family_native_stdin_argv(),
}),
Agent::Cursor => Some(AgentCliCommand {
binary: CursorWrapper.binary().to_string(),
args: claude_family_native_stdin_argv(),
}),
Agent::Junie => Some(AgentCliCommand {
binary: JunieWrapper.binary().to_string(),
args: claude_family_native_stdin_argv(),
}),
Agent::Codex => Some(AgentCliCommand {
binary: CodexWrapper.binary().to_string(),
args: vec!["exec".to_string(), "--json".to_string(), "-".to_string()],
}),
_ => None,
}
}
fn claude_family_native_stdin_argv() -> Vec<String> {
vec![
"-p".to_string(),
"--output-format".to_string(),
"stream-json".to_string(),
"--verbose".to_string(),
]
}
pub fn launch_model_selection(agent: Agent, model: &str) -> (Vec<String>, Vec<(String, String)>) {
match agent {
Agent::Claude => ClaudeWrapper.launch_model_selection(model),
Agent::Cursor => CursorWrapper.launch_model_selection(model),
Agent::Junie => JunieWrapper.launch_model_selection(model),
Agent::Copilot => CopilotWrapper.launch_model_selection(model),
Agent::Codex => CodexWrapper.launch_model_selection(model),
Agent::Gemini => GeminiWrapper.launch_model_selection(model),
Agent::Grok => GrokWrapper.launch_model_selection(model),
Agent::Xai => XaiWrapper.launch_model_selection(model),
Agent::Cline => ClineWrapper.launch_model_selection(model),
}
}
pub fn launch_auto_mode(agent: Agent) -> Vec<String> {
match agent {
Agent::Claude => ClaudeWrapper.launch_auto_mode(),
Agent::Cursor => CursorWrapper.launch_auto_mode(),
Agent::Junie => JunieWrapper.launch_auto_mode(),
Agent::Copilot => CopilotWrapper.launch_auto_mode(),
Agent::Codex => CodexWrapper.launch_auto_mode(),
Agent::Gemini => GeminiWrapper.launch_auto_mode(),
Agent::Grok => GrokWrapper.launch_auto_mode(),
Agent::Xai => XaiWrapper.launch_auto_mode(),
Agent::Cline => ClineWrapper.launch_auto_mode(),
}
}
pub fn launch_local_inference(
agent: Agent,
base_url: &str,
api_key: &str,
local_model: &str,
) -> (Vec<String>, Vec<(String, String)>) {
match agent {
Agent::Claude => ClaudeWrapper.launch_local_inference(base_url, api_key, local_model),
Agent::Cursor => CursorWrapper.launch_local_inference(base_url, api_key, local_model),
Agent::Codex => CodexWrapper.launch_local_inference(base_url, api_key, local_model),
_ => (Vec::new(), Vec::new()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use agent_common::claude_family_native_argv;
use cli_common::Agent;
#[test]
fn native_base_matches_claude_family_and_distinct_agents() {
let p = "do the thing";
assert_eq!(native_base_command(Agent::Cursor, p).binary, "cursor");
assert_eq!(
native_base_command(Agent::Claude, p).args,
claude_family_native_argv(p)
);
assert_eq!(
native_base_command(Agent::Junie, p).args,
claude_family_native_argv(p)
);
assert_eq!(
native_base_command(Agent::Cursor, p).args,
claude_family_native_argv(p)
);
assert_eq!(
native_base_command(Agent::Codex, p).args,
vec!["exec".to_string(), "--json".to_string(), p.to_string()]
);
assert_eq!(
native_base_command(Agent::Cline, p).args,
vec!["chat".to_string(), p.to_string()]
);
assert_eq!(
native_base_command(Agent::Copilot, p).args,
vec!["-p".to_string(), p.to_string()]
);
}
#[test]
fn caretta_native_command_appends_overrides_after_base() {
let extra = vec!["--model".to_string(), "m".to_string()];
let cmd = caretta_native_command(Agent::Gemini, "hi", &extra);
assert_eq!(cmd.args[0..2], ["-p", "hi"]);
assert_eq!(cmd.args[2..], ["--model", "m"]);
}
#[test]
fn oversized_claude_prompt_uses_stdin_transport() {
let prompt = "x".repeat(PROMPT_STDIN_BYTE_THRESHOLD + 1);
let cmd = caretta_native_command_with_prompt_transport(Agent::Claude, &prompt, &[]);
assert_eq!(cmd.command.binary, "claude");
assert_eq!(cmd.command.args, claude_family_native_stdin_argv());
assert_eq!(cmd.prompt_transport, PromptTransport::Stdin);
assert!(!cmd.command.args.iter().any(|arg| arg == &prompt));
}
#[test]
fn oversized_codex_prompt_uses_stdin_transport() {
let prompt = "x".repeat(PROMPT_STDIN_BYTE_THRESHOLD + 1);
let extra = vec!["--dangerously-bypass-approvals-and-sandbox".to_string()];
let cmd = caretta_native_command_with_prompt_transport(Agent::Codex, &prompt, &extra);
assert_eq!(cmd.command.binary, "codex");
assert_eq!(
cmd.command.args,
vec![
"exec".to_string(),
"--json".to_string(),
"-".to_string(),
"--dangerously-bypass-approvals-and-sandbox".to_string()
]
);
assert_eq!(cmd.prompt_transport, PromptTransport::Stdin);
assert!(!cmd.command.args.iter().any(|arg| arg == &prompt));
}
#[test]
fn small_prompts_keep_existing_argv_shape() {
let cmd = caretta_native_command_with_prompt_transport(Agent::Claude, "small", &[]);
assert_eq!(cmd.command.args, claude_family_native_argv("small"));
assert_eq!(cmd.prompt_transport, PromptTransport::Argv);
}
#[test]
fn xai_model_selection_uses_env_not_args() {
let (args, env) = launch_model_selection(Agent::Xai, "grok-3");
assert!(args.is_empty());
assert_eq!(
env,
vec![("COPILOT_MODEL".to_string(), "grok-3".to_string())]
);
}
}