use super::AgentDriver;
use crate::phase_id::PhaseId;
pub struct AntigravityDriver;
impl AgentDriver for AntigravityDriver {
fn name(&self) -> &'static str {
"Antigravity"
}
fn render_prompt(&self, intent: &crate::prompt::StageIntent) -> String {
crate::prompt::render_claude_style(intent)
}
fn build_command(
&self,
_phase: PhaseId,
_prompt: &str,
_extra_writable_roots: &[std::path::PathBuf],
) -> (&'static str, Vec<String>) {
(
"agy",
vec![
"--input-format".into(),
"stream-json".into(),
"--output-format".into(),
"stream-json".into(),
"--print-timeout".into(),
"60m".into(),
],
)
}
fn parse_completion(&self, output: &str) -> Option<crate::agent_result::AgentResult> {
crate::agent_result::parse_antigravity_event_result(output)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::agent_result::AgentStatus;
#[test]
fn antigravity_driver_build_command_is_exact() {
let (program, args) = AntigravityDriver.build_command(PhaseId::new(0), "x", &[]);
assert_eq!(program, "agy");
assert_eq!(
args,
vec![
"--input-format".to_string(),
"stream-json".to_string(),
"--output-format".to_string(),
"stream-json".to_string(),
"--print-timeout".to_string(),
"60m".to_string(),
],
"exact argv: no -p, no --dangerously-skip-permissions, no prompt, \
--print-timeout above the 5m default (D-01/D-02/F3)"
);
}
#[test]
fn antigravity_driver_render_prompt_delegates_to_claude_style() {
let intent =
crate::prompt::StageIntent::for_stage(crate::stage::Stage::Code, PhaseId::new(7));
let rendered = AntigravityDriver.render_prompt(&intent);
assert_eq!(
rendered,
crate::prompt::render_claude_style(&intent),
"D-05"
);
assert!(
rendered.contains("DEVFLOW_RESULT"),
"contract: prompt must carry the marker instruction"
);
}
#[test]
fn antigravity_driver_parse_completion_delegates() {
let capture = concat!(
"{\"event\":\"init\",\"model\":\"stub\"}\n",
"{\"event\":\"result\",\"result\":{\"status\":\"SUCCESS\",\"response\":\"DEVFLOW_RESULT: {\\\"status\\\":\\\"success\\\"}\"}}\n",
);
let got = AntigravityDriver
.parse_completion(capture)
.expect("marker must resolve");
assert_eq!(got.status, AgentStatus::Success);
let error = concat!(
"{\"event\":\"init\",\"model\":\"stub\"}\n",
"{\"event\":\"result\",\"result\":{\"status\":\"ERROR\",\"response\":\"\",\"error\":\"stream input message is missing the \\\"event\\\" field\"}}\n",
);
let got = AntigravityDriver
.parse_completion(error)
.expect("ERROR envelope must resolve");
assert_eq!(got.status, AgentStatus::Failed);
assert!(
got.reason
.as_deref()
.unwrap()
.contains("missing the \"event\" field")
);
let marker_less = concat!(
"{\"event\":\"init\",\"model\":\"stub\"}\n",
"{\"event\":\"result\",\"result\":{\"status\":\"SUCCESS\",\"response\":\"all done\"}}\n",
);
assert!(AntigravityDriver.parse_completion(marker_less).is_none());
}
#[test]
fn antigravity_driver_spawn_argv_smoke() {
let dir = tempfile::tempdir().unwrap();
let bin_dir = dir.path().join("bin");
let argv_file = dir.path().join("argv.txt");
let turn_file = dir.path().join("turn.txt");
std::fs::create_dir_all(&bin_dir).unwrap();
let stub = format!(
r#"#!/bin/sh
printf '%s\n' "$@" > '{}'
IFS= read -r turn
printf '%s\n' "$turn" > '{}'
printf '%s\n' '{{"event":"init","model":"stub","inputFormat":"stream-json","outputFormat":"stream-json"}}'
printf '%s\n' '{{"event":"result","result":{{"status":"SUCCESS","response":"DEVFLOW_RESULT: {{\"status\":\"success\"}}"}}}}'
"#,
argv_file.display(),
turn_file.display(),
);
std::fs::write(bin_dir.join("agy"), stub).unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(bin_dir.join("agy"), std::fs::Permissions::from_mode(0o755))
.unwrap();
}
let (program, args) = AntigravityDriver.build_command(PhaseId::new(0), "x", &[]);
let out = std::process::Command::new(program)
.args(&args)
.env("PATH", &bin_dir)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()
.and_then(|mut child| {
use std::io::Write;
child.stdin.take().unwrap().write_all(
b"{\"event\":\"user\",\"message\":{\"role\":\"user\",\"content\":\"x\"}}\n",
)?;
child.wait_with_output()
})
.expect("stub agy must spawn");
assert!(out.status.success(), "stub exited {:?}", out.status.code());
let received: Vec<String> = std::fs::read_to_string(&argv_file)
.unwrap()
.lines()
.map(str::to_string)
.collect();
assert_eq!(
received, args,
"spawned argv must equal build_command's argv: {received:?}"
);
let parsed = AntigravityDriver
.parse_completion(&String::from_utf8_lossy(&out.stdout))
.expect("stub stream must parse");
assert_eq!(parsed.status, AgentStatus::Success);
let turn = std::fs::read_to_string(&turn_file).unwrap();
assert!(
turn.contains("\"event\":\"user\""),
"first turn must be event-key: {turn}"
);
assert!(!turn.contains("\"type\":"), "no type key allowed: {turn}");
}
#[test]
fn antigravity_driver_name_is_correct() {
assert_eq!(AntigravityDriver.name(), "Antigravity");
}
}