mod catalog;
pub(crate) mod stream;
pub use catalog::Catalog;
use super::adapter::{Adapter, Invocation, SkillLoading};
use super::interpretation::{Interpretation, Retained};
use super::settings::{Terminal, ask_setting};
use super::{Choice, Harness, ModelAndEffort, Settings};
use crate::progress;
use anyhow::Result;
use std::path::Path;
pub struct Agy;
pub const REGISTRATION: (Harness, &dyn Adapter) = (Harness::Agy, &Agy);
impl Adapter for Agy {
fn name(&self) -> &'static str {
"agy"
}
fn product_name(&self) -> &'static str {
"Antigravity CLI"
}
fn project_skills(&self) -> &'static str {
".agents/skills"
}
fn skill_loading(&self) -> SkillLoading {
SkillLoading::Slash
}
fn stop_signals(&self) -> &'static [libc::c_int] {
&[libc::SIGTERM]
}
fn settings<'a>(&self, settings: &'a Settings) -> &'a ModelAndEffort {
&settings.agy
}
fn settings_mut<'a>(&self, settings: &'a mut Settings) -> &'a mut ModelAndEffort {
&mut settings.agy
}
fn environment(&self) -> &'static [(&'static str, &'static str)] {
&[("AGY_CLI_DISABLE_AUTO_UPDATE", "true")]
}
fn session(&self, choice: &Choice, resume: Option<&str>, prompt: &str) -> Invocation {
let mut args: Vec<String> = [
"-p",
"--dangerously-skip-permissions",
"--output-format",
"stream-json",
]
.map(String::from)
.to_vec();
if let Some(model) = &choice.model {
args.extend(["--model".to_string(), model.clone()]);
}
if let Some(effort) = &choice.effort {
args.extend(["--effort".to_string(), effort.clone()]);
}
if let Some(id) = resume {
args.extend(["--conversation".to_string(), id.to_string()]);
}
args.push(self.skill_loading().prompt(prompt));
Invocation { args, stdin: None }
}
fn check(&self, choice: &mut Choice) -> Result<()> {
if choice.model.is_none() && choice.effort.is_none() {
return Ok(());
}
progress::step("checking the Model and Effort against agy models");
let settled = Catalog::read()?.settle(choice.model.as_deref(), choice.effort.as_deref())?;
choice.model = settled.model;
choice.effort = settled.effort;
Ok(())
}
fn ask_settings(
&self,
outside: &mut dyn Terminal,
current: &ModelAndEffort,
) -> Result<Option<ModelAndEffort>> {
let catalog = match Catalog::read() {
Ok(catalog) => catalog,
Err(error) => {
crate::interrupt::check()?;
outside.say(format!("{error:#}\nThe harness settings stay as they are; rerun `thirdshift setup` once agy models works."));
return Ok(None);
}
};
outside.say(format!(
"{}'s Models: {}",
self.product_name(),
catalog.listed().join(", ")
));
let model = loop {
let model = ask_setting(outside, "Model", Harness::Agy, current.model.as_deref())?;
match catalog.model_name(model.as_deref()) {
Ok(model) => break model,
Err(error) => outside.say(format!("{error:#}")),
}
};
outside.say(format!(
"Efforts {} supports: {}",
model.as_deref().unwrap_or("agy"),
catalog.efforts(model.as_deref()).join(", ")
));
loop {
let effort = ask_setting(outside, "Effort", Harness::Agy, current.effort.as_deref())?;
match catalog.settle(model.as_deref(), effort.as_deref()) {
Ok(settled) => return Ok(Some(settled)),
Err(error) => outside.say(format!("{error:#}")),
}
}
}
fn link_instruction_fallback(&self, worktree: &Path) -> Result<()> {
super::instructions::link_fallback(worktree, "GEMINI.md", &["AGENTS.md"])
}
fn interpretation(&self, _worktree: &Path, _prompt: &str) -> Interpretation {
Interpretation::new(
self.name(),
Box::new(stream::AgyProgress::default()),
Retained::None,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::harness::ChosenBy;
#[test]
fn a_resume_reuses_every_flag_and_environment_and_the_conversation_id() {
let chosen = Choice {
harness: Harness::Agy,
model: Some("gemini-3.8-flash".to_string()),
effort: Some("medium".to_string()),
chosen_by: ChosenBy::Command,
};
let adapter = chosen.harness.adapter();
let resume = adapter.session(&chosen, Some("recorded-conversation"), "Continue.");
assert_eq!(
resume.args,
[
"-p",
"--dangerously-skip-permissions",
"--output-format",
"stream-json",
"--model",
"gemini-3.8-flash",
"--effort",
"medium",
"--conversation",
"recorded-conversation",
"Continue."
]
);
assert_eq!(resume.stdin, None);
assert_eq!(
adapter.environment(),
[("AGY_CLI_DISABLE_AUTO_UPDATE", "true")]
);
}
}