use std::path::Path;
use std::process::Command;
use super::backend::{AgentBackend, AgentBackendError, BuildCommandRequest};
use super::prompt::{PromptPreparationRequest, prepare_prompt_text};
pub(super) struct GeminiBackend;
impl AgentBackend for GeminiBackend {
fn setup(&self, _folder: &Path) -> Result<(), AgentBackendError> {
Ok(())
}
fn build_command<'request>(
&'request self,
request: BuildCommandRequest<'request>,
) -> Result<Command, AgentBackendError> {
Ok(build_app_server_command(request))
}
}
fn build_app_server_command(request: BuildCommandRequest<'_>) -> Command {
let BuildCommandRequest {
attachments: _attachments,
folder,
prompt: _prompt,
request_kind: _request_kind,
model,
reasoning_level: _reasoning_level,
} = request;
let mut command = Command::new("gemini");
command
.arg("--acp")
.arg("--model")
.arg(model)
.current_dir(folder);
command
}
pub(super) fn build_prompt_stdin_payload(
request: BuildCommandRequest<'_>,
) -> Result<Vec<u8>, AgentBackendError> {
let prompt = prepare_prompt_text(PromptPreparationRequest {
instruction_delivery_mode: if request.request_kind.is_resume() {
super::instruction::InstructionDeliveryMode::BootstrapWithReplay
} else {
super::instruction::InstructionDeliveryMode::BootstrapFull
},
prompt: request.prompt,
protocol_profile: request.request_kind.protocol_profile(),
replay_session_output: request.request_kind.session_output(),
})?;
Ok(prompt.into_bytes())
}
#[cfg(test)]
mod tests {
use tempfile::tempdir;
use super::*;
use crate::domain::agent::ReasoningLevel;
use crate::infra::channel::AgentRequestKind;
fn session_start_request_kind() -> AgentRequestKind {
AgentRequestKind::SessionStart
}
fn utility_request_kind() -> AgentRequestKind {
AgentRequestKind::UtilityPrompt
}
#[test]
fn test_gemini_setup_creates_no_files() {
let temp_directory = tempdir().expect("failed to create temp dir");
let backend = GeminiBackend;
AgentBackend::setup(&backend, temp_directory.path()).expect("setup should succeed");
assert_eq!(
std::fs::read_dir(temp_directory.path())
.expect("failed to read dir")
.count(),
0
);
}
#[test]
fn test_gemini_prompt_stdin_payload_includes_repo_root_path_instructions() {
let temp_directory = tempdir().expect("failed to create temp dir");
let prompt = String::from_utf8(
build_prompt_stdin_payload(BuildCommandRequest {
attachments: &[],
folder: temp_directory.path(),
prompt: "Plan prompt",
request_kind: &session_start_request_kind(),
model: "gemini-3-flash-preview",
reasoning_level: ReasoningLevel::default(),
})
.expect("prompt payload should build"),
)
.expect("prompt payload should be utf-8");
assert!(prompt.contains("repository-root-relative POSIX paths"));
assert!(prompt.contains("Paths must be relative to the repository root."));
assert!(prompt.contains("summary"));
}
#[test]
fn test_gemini_build_command_uses_acp_runtime_command() {
let temp_directory = tempdir().expect("failed to create temp dir");
let backend = GeminiBackend;
let command = AgentBackend::build_command(
&backend,
BuildCommandRequest {
attachments: &[],
folder: temp_directory.path(),
prompt: "Generate title",
request_kind: &utility_request_kind(),
model: "gemini-3-flash-preview",
reasoning_level: ReasoningLevel::default(),
},
)
.expect("command should build");
let args = command
.get_args()
.map(|arg| arg.to_string_lossy().into_owned())
.collect::<Vec<_>>();
assert_eq!(args, vec!["--acp", "--model", "gemini-3-flash-preview"]);
assert_eq!(command.get_current_dir(), Some(temp_directory.path()));
}
}