use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AgentKind {
Antigravity,
Gemini,
Claude,
Codex,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AgentCliInfo {
pub executable_name: &'static str,
pub kind: AgentKind,
pub version: AgentCliVersion,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AgentCliVersion {
Loading,
Unknown,
Value(String),
}
impl AgentCliInfo {
pub fn new(kind: AgentKind, version: Option<String>) -> Self {
Self {
executable_name: kind.executable_name(),
kind,
version: version.map_or(AgentCliVersion::Unknown, AgentCliVersion::Value),
}
}
pub fn loading(kind: AgentKind) -> Self {
Self {
executable_name: kind.executable_name(),
kind,
version: AgentCliVersion::Loading,
}
}
pub fn from_kinds(agent_kinds: &[AgentKind]) -> Vec<Self> {
agent_kinds
.iter()
.copied()
.map(|agent_kind| Self::new(agent_kind, None))
.collect()
}
pub fn loading_from_kinds(agent_kinds: &[AgentKind]) -> Vec<Self> {
agent_kinds.iter().copied().map(Self::loading).collect()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AgentModel {
Gpt55,
Gemini3FlashPreview,
Gemini35Flash,
Gemini31FlashLitePreview,
Gemini31ProPreview,
Gpt54Mini,
Gpt53CodexSpark,
ClaudeOpus48,
ClaudeSonnet5,
ClaudeFable5,
ClaudeHaiku4520251001,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AgentSelection {
kind: AgentKind,
model: AgentModel,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ReasoningLevel {
Low,
Medium,
#[default]
High,
XHigh,
}
impl AgentSelection {
#[must_use]
pub fn new(kind: AgentKind, model: AgentModel) -> Self {
let model = if kind.supports_model(model) {
model
} else {
kind.default_model()
};
Self { kind, model }
}
#[must_use]
pub fn kind(self) -> AgentKind {
self.kind
}
#[must_use]
pub fn model(self) -> AgentModel {
self.model
}
}
pub trait AgentSelectionMetadata {
fn name(&self) -> &'static str;
fn description(&self) -> &'static str;
}
impl AgentModel {
pub fn as_str(self) -> &'static str {
match self {
Self::Gpt55 => "gpt-5.5",
Self::Gemini3FlashPreview => "gemini-3-flash-preview",
Self::Gemini35Flash => "gemini-3.5-flash",
Self::Gemini31FlashLitePreview => "gemini-3.1-flash-lite-preview",
Self::Gemini31ProPreview => "gemini-3.1-pro-preview",
Self::Gpt54Mini => "gpt-5.4-mini",
Self::Gpt53CodexSpark => "gpt-5.3-codex-spark",
Self::ClaudeOpus48 => "claude-opus-4-8",
Self::ClaudeSonnet5 => "claude-sonnet-5",
Self::ClaudeFable5 => "claude-fable-5",
Self::ClaudeHaiku4520251001 => "claude-haiku-4-5-20251001",
}
}
pub fn provider_model_str(self) -> &'static str {
self.as_str()
}
pub fn parse_persisted(value: &str) -> Result<Self, String> {
match value {
"claude-opus-4-6" | "claude-opus-4-7" => Ok(Self::ClaudeOpus48),
"claude-sonnet-4-6" => Ok(Self::ClaudeSonnet5),
"gpt-5.4" => Ok(Self::Gpt55),
_ => value.parse(),
}
}
}
pub fn parse_persisted_session_agent_model(
agent_value: Option<&str>,
model_value: &str,
) -> AgentSelection {
let parsed_agent = agent_value
.filter(|value| !value.trim().is_empty())
.and_then(|value| value.parse::<AgentKind>().ok());
if let Some(agent_kind) = parsed_agent {
return parse_model_for_persisted_agent(agent_kind, model_value);
}
let model = AgentModel::parse_persisted(model_value)
.unwrap_or_else(|_| AgentKind::Antigravity.default_model());
let agent_kind = legacy_agent_kind_for_model_value(model_value, model);
AgentSelection::new(agent_kind, model)
}
fn parse_model_for_persisted_agent(agent_kind: AgentKind, model_value: &str) -> AgentSelection {
if let Some(model) = agent_kind.parse_model(model_value) {
return AgentSelection::new(agent_kind, model);
}
if let Ok(model) = AgentModel::parse_persisted(model_value)
&& agent_kind.supports_model(model)
{
return AgentSelection::new(agent_kind, model);
}
AgentSelection::new(agent_kind, agent_kind.default_model())
}
fn legacy_agent_kind_for_model_value(model_value: &str, model: AgentModel) -> AgentKind {
if model_value.starts_with("claude-") {
return AgentKind::Claude;
}
if model_value.starts_with("gpt-") {
return AgentKind::Codex;
}
if model_value.starts_with("gemini-") {
return AgentKind::Antigravity;
}
AgentKind::ALL
.iter()
.copied()
.find(|agent_kind| agent_kind.supports_model(model))
.unwrap_or(AgentKind::Antigravity)
}
#[must_use]
pub fn selectable_models_for_agent_kinds(agent_kinds: &[AgentKind]) -> Vec<AgentModel> {
let mut models = Vec::new();
for model in agent_kinds
.iter()
.flat_map(|agent_kind| agent_kind.models())
.copied()
{
if !models.contains(&model) {
models.push(model);
}
}
models
}
#[must_use]
pub fn resolve_model_for_available_agent_kinds(
model: AgentModel,
agent_kinds: &[AgentKind],
fallback_model: AgentModel,
) -> AgentModel {
if agent_kinds
.iter()
.any(|agent_kind| agent_kind.supports_model(model))
{
return model;
}
if agent_kinds
.iter()
.any(|agent_kind| agent_kind.supports_model(fallback_model))
{
return fallback_model;
}
agent_kinds
.first()
.copied()
.map_or(fallback_model, AgentKind::default_model)
}
#[must_use]
pub fn resolve_agent_kind_for_model(
model: AgentModel,
agent_kinds: &[AgentKind],
fallback_agent_kind: AgentKind,
) -> AgentKind {
agent_kinds
.iter()
.copied()
.find(|agent_kind| agent_kind.supports_model(model))
.unwrap_or(fallback_agent_kind)
}
#[must_use]
pub fn resolve_agent_selection_for_model(
model: AgentModel,
preferred_agent_kind: AgentKind,
agent_kinds: &[AgentKind],
) -> AgentSelection {
let agent_kind = if preferred_agent_kind.supports_model(model) {
preferred_agent_kind
} else {
resolve_agent_kind_for_model(model, agent_kinds, preferred_agent_kind)
};
AgentSelection::new(agent_kind, model)
}
#[must_use]
pub fn resolve_prompt_model_agent_kind(
session_agent_kind: AgentKind,
agent_kinds: &[AgentKind],
) -> Option<AgentKind> {
if agent_kinds.contains(&session_agent_kind) {
return Some(session_agent_kind);
}
agent_kinds.first().copied()
}
impl ReasoningLevel {
pub const ALL: [Self; 4] = [Self::Low, Self::Medium, Self::High, Self::XHigh];
pub fn as_str(self) -> &'static str {
match self {
Self::Low => "low",
Self::Medium => "medium",
Self::High => "high",
Self::XHigh => "xhigh",
}
}
pub fn codex(self) -> &'static str {
match self {
Self::Low => "low",
Self::Medium => "medium",
Self::High => "high",
Self::XHigh => "xhigh",
}
}
pub fn claude(self) -> &'static str {
match self {
Self::Low => "low",
Self::Medium => "medium",
Self::High => "high",
Self::XHigh => "max",
}
}
pub fn description(self) -> &'static str {
match self {
Self::Low => "Fastest responses with lighter reasoning.",
Self::Medium => "Balanced speed and reasoning depth.",
Self::High => "Deeper reasoning for tougher tasks.",
Self::XHigh => "Maximum reasoning effort for the hardest tasks.",
}
}
}
impl FromStr for ReasoningLevel {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"low" => Ok(Self::Low),
"medium" => Ok(Self::Medium),
"high" => Ok(Self::High),
"xhigh" => Ok(Self::XHigh),
other => Err(format!("unknown reasoning level: {other}")),
}
}
}
impl FromStr for AgentModel {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"gemini-3-flash-preview" => Ok(Self::Gemini3FlashPreview),
"gemini-3.5-flash" => Ok(Self::Gemini35Flash),
"gemini-3.1-flash-lite-preview" => Ok(Self::Gemini31FlashLitePreview),
"gemini-3.1-pro-preview" => Ok(Self::Gemini31ProPreview),
"gpt-5.5" => Ok(Self::Gpt55),
"gpt-5.4-mini" => Ok(Self::Gpt54Mini),
"gpt-5.3-codex-spark" => Ok(Self::Gpt53CodexSpark),
"claude-opus-4-8" => Ok(Self::ClaudeOpus48),
"claude-sonnet-5" => Ok(Self::ClaudeSonnet5),
"claude-fable-5" => Ok(Self::ClaudeFable5),
"claude-haiku-4-5-20251001" => Ok(Self::ClaudeHaiku4520251001),
other => Err(format!("unknown model: {other}")),
}
}
}
impl AgentSelectionMetadata for AgentModel {
fn name(&self) -> &'static str {
(*self).provider_model_str()
}
fn description(&self) -> &'static str {
match self {
Self::Gemini31ProPreview => "Higher-quality Gemini model for deeper reasoning.",
Self::Gemini35Flash => "Fast Gemini model for current Flash workloads.",
Self::Gemini31FlashLitePreview => {
"Lightweight Gemini model for fast, cost-conscious iterations."
}
Self::Gemini3FlashPreview => "Fast Gemini model for quick iterations.",
Self::Gpt55 => "Newer Codex model with stronger coding performance when available.",
Self::Gpt54Mini => "Small, fast Codex model for simpler coding tasks.",
Self::Gpt53CodexSpark => "Codex spark model for quick coding iterations.",
Self::ClaudeOpus48 => "Latest Claude Opus model for complex tasks.",
Self::ClaudeSonnet5 => "Balanced Claude model for quality and latency.",
Self::ClaudeFable5 => "Claude Fable model for creative, narrative-heavy tasks.",
Self::ClaudeHaiku4520251001 => "Fast Claude model for lighter tasks.",
}
}
}
impl AgentKind {
pub const ALL: &[AgentKind] = &[
AgentKind::Gemini,
AgentKind::Antigravity,
AgentKind::Claude,
AgentKind::Codex,
];
pub fn executable_name(self) -> &'static str {
match self {
Self::Antigravity => "agy",
Self::Gemini => "gemini",
Self::Claude => "claude",
Self::Codex => "codex",
}
}
pub fn default_model(self) -> AgentModel {
match self {
Self::Antigravity | Self::Gemini => AgentModel::Gemini31ProPreview,
Self::Claude => AgentModel::ClaudeFable5,
Self::Codex => AgentModel::Gpt55,
}
}
pub fn model_str(self, model: AgentModel) -> Option<&'static str> {
if !self.supports_model(model) {
return None;
}
Some(model.as_str())
}
pub fn models(self) -> &'static [AgentModel] {
const ANTIGRAVITY_MODELS: &[AgentModel] = &[
AgentModel::Gemini31ProPreview,
AgentModel::Gemini35Flash,
AgentModel::Gemini31FlashLitePreview,
AgentModel::Gemini3FlashPreview,
];
const GEMINI_MODELS: &[AgentModel] = &[
AgentModel::Gemini31ProPreview,
AgentModel::Gemini35Flash,
AgentModel::Gemini31FlashLitePreview,
AgentModel::Gemini3FlashPreview,
];
const CLAUDE_MODELS: &[AgentModel] = &[
AgentModel::ClaudeFable5,
AgentModel::ClaudeOpus48,
AgentModel::ClaudeSonnet5,
AgentModel::ClaudeHaiku4520251001,
];
const CODEX_MODELS: &[AgentModel] = &[
AgentModel::Gpt55,
AgentModel::Gpt54Mini,
AgentModel::Gpt53CodexSpark,
];
match self {
Self::Antigravity => ANTIGRAVITY_MODELS,
Self::Gemini => GEMINI_MODELS,
Self::Claude => CLAUDE_MODELS,
Self::Codex => CODEX_MODELS,
}
}
pub fn parse_model(self, value: &str) -> Option<AgentModel> {
let model = value.parse::<AgentModel>().ok()?;
if !self.supports_model(model) {
return None;
}
Some(model)
}
pub fn supports_model(self, model: AgentModel) -> bool {
self.models().contains(&model)
}
}
impl AgentSelectionMetadata for AgentKind {
fn name(&self) -> &'static str {
match self {
Self::Antigravity => "antigravity",
Self::Gemini => "gemini",
Self::Claude => "claude",
Self::Codex => "codex",
}
}
fn description(&self) -> &'static str {
match self {
Self::Antigravity => "Google Antigravity CLI agent.",
Self::Gemini => "Google Gemini CLI agent.",
Self::Claude => "Anthropic Claude Code agent.",
Self::Codex => "OpenAI Codex CLI agent.",
}
}
}
impl AgentSelectionMetadata for ReasoningLevel {
fn name(&self) -> &'static str {
(*self).as_str()
}
fn description(&self) -> &'static str {
(*self).description()
}
}
impl fmt::Display for AgentKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
impl FromStr for AgentKind {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"antigravity" | "agy" => Ok(Self::Antigravity),
"gemini" => Ok(Self::Gemini),
"claude" => Ok(Self::Claude),
"codex" => Ok(Self::Codex),
other => Err(format!("unknown agent kind: {other}")),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_model_returns_none_for_models_from_other_providers() {
let claude_kind = AgentKind::Claude;
let antigravity_model = AgentModel::Gemini3FlashPreview.as_str();
let parsed_model = claude_kind.parse_model(antigravity_model);
assert_eq!(parsed_model, None);
}
#[test]
fn test_parse_model_parses_gpt_55() {
let codex_kind = AgentKind::Codex;
let parsed_model = codex_kind.parse_model("gpt-5.5");
assert_eq!(parsed_model, Some(AgentModel::Gpt55));
}
#[test]
fn test_parse_model_parses_antigravity() {
let antigravity_kind = AgentKind::Antigravity;
let parsed_model = antigravity_kind.parse_model("gemini-3.5-flash");
assert_eq!(parsed_model, Some(AgentModel::Gemini35Flash));
}
#[test]
fn test_parse_model_parses_direct_gemini() {
let gemini_kind = AgentKind::Gemini;
let parsed_model = gemini_kind.parse_model("gemini-3.5-flash");
assert_eq!(parsed_model, Some(AgentModel::Gemini35Flash));
}
#[test]
fn test_parse_model_rejects_retired_gpt_54() {
let codex_kind = AgentKind::Codex;
let parsed_gpt_54 = codex_kind.parse_model("gpt-5.4");
assert_eq!(parsed_gpt_54, None);
}
#[test]
fn test_parse_model_parses_gpt_54_mini() {
let codex_kind = AgentKind::Codex;
let parsed_model = codex_kind.parse_model("gpt-5.4-mini");
assert_eq!(parsed_model, Some(AgentModel::Gpt54Mini));
}
#[test]
fn test_parse_model_rejects_retired_claude_aliases() {
let claude_kind = AgentKind::Claude;
let parsed_opus_46 = claude_kind.parse_model("claude-opus-4-6");
let parsed_opus_47 = claude_kind.parse_model("claude-opus-4-7");
let parsed_sonnet_46 = claude_kind.parse_model("claude-sonnet-4-6");
assert_eq!(parsed_opus_46, None);
assert_eq!(parsed_opus_47, None);
assert_eq!(parsed_sonnet_46, None);
}
#[test]
fn test_parse_model_parses_current_claude_models() {
let claude_kind = AgentKind::Claude;
let parsed_sonnet_5 = claude_kind.parse_model("claude-sonnet-5");
let parsed_fable_5 = claude_kind.parse_model("claude-fable-5");
let parsed_haiku_45 = claude_kind.parse_model("claude-haiku-4-5-20251001");
assert_eq!(parsed_sonnet_5, Some(AgentModel::ClaudeSonnet5));
assert_eq!(parsed_fable_5, Some(AgentModel::ClaudeFable5));
assert_eq!(parsed_haiku_45, Some(AgentModel::ClaudeHaiku4520251001));
}
#[test]
fn test_parse_persisted_handles_supported_and_retired_models() {
let parsed_opus_46 = AgentModel::parse_persisted("claude-opus-4-6");
let parsed_opus_47 = AgentModel::parse_persisted("claude-opus-4-7");
let parsed_sonnet_46 = AgentModel::parse_persisted("claude-sonnet-4-6");
let parsed_sonnet_5 = AgentModel::parse_persisted("claude-sonnet-5");
let parsed_gpt_54 = AgentModel::parse_persisted("gpt-5.4");
let parsed_gemini_35_flash = AgentModel::parse_persisted("gemini-3.5-flash");
assert_eq!(parsed_opus_46, Ok(AgentModel::ClaudeOpus48));
assert_eq!(parsed_opus_47, Ok(AgentModel::ClaudeOpus48));
assert_eq!(parsed_sonnet_46, Ok(AgentModel::ClaudeSonnet5));
assert_eq!(parsed_sonnet_5, Ok(AgentModel::ClaudeSonnet5));
assert_eq!(parsed_gpt_54, Ok(AgentModel::Gpt55));
assert_eq!(parsed_gemini_35_flash, Ok(AgentModel::Gemini35Flash));
}
#[test]
fn test_parse_persisted_session_agent_model_prefers_saved_agent() {
let selection = parse_persisted_session_agent_model(Some("codex"), "gemini-3.5-flash");
assert_eq!(selection.kind(), AgentKind::Codex);
assert_eq!(selection.model(), AgentKind::Codex.default_model());
}
#[test]
fn test_parse_persisted_session_agent_model_preserves_antigravity_models() {
let selection =
parse_persisted_session_agent_model(Some("antigravity"), "gemini-3.5-flash");
assert_eq!(selection.kind(), AgentKind::Antigravity);
assert_eq!(selection.model(), AgentModel::Gemini35Flash);
}
#[test]
fn test_parse_persisted_session_agent_model_supports_legacy_rows() {
let selection = parse_persisted_session_agent_model(None, "claude-opus-4-6");
assert_eq!(selection.kind(), AgentKind::Claude);
assert_eq!(selection.model(), AgentModel::ClaudeOpus48);
}
#[test]
fn test_parse_persisted_session_agent_model_defaults_legacy_gemini_to_antigravity() {
let selection = parse_persisted_session_agent_model(None, "gemini-3.5-flash");
assert_eq!(selection.kind(), AgentKind::Antigravity);
assert_eq!(selection.model(), AgentModel::Gemini35Flash);
}
#[test]
fn test_reasoning_level_from_str_parses_supported_values() {
let low_level = "low".parse::<ReasoningLevel>();
let medium_level = "medium".parse::<ReasoningLevel>();
let high_level = "high".parse::<ReasoningLevel>();
let xhigh_level = "xhigh".parse::<ReasoningLevel>();
assert_eq!(low_level, Ok(ReasoningLevel::Low));
assert_eq!(medium_level, Ok(ReasoningLevel::Medium));
assert_eq!(high_level, Ok(ReasoningLevel::High));
assert_eq!(xhigh_level, Ok(ReasoningLevel::XHigh));
}
#[test]
fn test_reasoning_level_from_str_rejects_unknown_values() {
let parse_result = "minimal".parse::<ReasoningLevel>();
assert!(parse_result.is_err());
}
#[test]
fn test_codex_models_are_supported_by_codex() {
let models = [
AgentModel::Gpt55,
AgentModel::Gpt54Mini,
AgentModel::Gpt53CodexSpark,
];
let supported = models.map(|model| AgentKind::Codex.supports_model(model));
let unsupported = models.map(|model| AgentKind::Claude.supports_model(model));
assert_eq!(supported, [true; 3]);
assert_eq!(unsupported, [false; 3]);
}
#[test]
fn test_claude_models_are_supported_by_claude() {
let models = [
AgentModel::ClaudeOpus48,
AgentModel::ClaudeSonnet5,
AgentModel::ClaudeFable5,
AgentModel::ClaudeHaiku4520251001,
];
let supported = models.map(|model| AgentKind::Claude.supports_model(model));
let unsupported = models.map(|model| AgentKind::Codex.supports_model(model));
assert_eq!(supported, [true; 4]);
assert_eq!(unsupported, [false; 4]);
}
#[test]
fn test_gemini_models_are_supported_by_gemini_and_antigravity() {
let models = [
AgentModel::Gemini31ProPreview,
AgentModel::Gemini35Flash,
AgentModel::Gemini31FlashLitePreview,
AgentModel::Gemini3FlashPreview,
];
let antigravity_supported =
models.map(|model| AgentKind::Antigravity.supports_model(model));
let gemini_supported = models.map(|model| AgentKind::Gemini.supports_model(model));
let codex_supported = models.map(|model| AgentKind::Codex.supports_model(model));
assert_eq!(antigravity_supported, [true; 4]);
assert_eq!(gemini_supported, [true; 4]);
assert_eq!(codex_supported, [false; 4]);
}
#[test]
fn test_antigravity_provider_model_str_returns_raw_gemini_model() {
let model = AgentModel::Gemini31FlashLitePreview;
let persisted_model = model.as_str();
let provider_model = model.provider_model_str();
assert_eq!(persisted_model, "gemini-3.1-flash-lite-preview");
assert_eq!(provider_model, "gemini-3.1-flash-lite-preview");
}
#[test]
fn test_reasoning_level_claude_maps_all_levels() {
assert_eq!(ReasoningLevel::Low.claude(), "low");
assert_eq!(ReasoningLevel::Medium.claude(), "medium");
assert_eq!(ReasoningLevel::High.claude(), "high");
assert_eq!(ReasoningLevel::XHigh.claude(), "max");
}
#[test]
fn test_reasoning_level_as_str_returns_stable_persisted_values() {
assert_eq!(ReasoningLevel::Low.as_str(), "low");
assert_eq!(ReasoningLevel::Medium.as_str(), "medium");
assert_eq!(ReasoningLevel::High.as_str(), "high");
assert_eq!(ReasoningLevel::XHigh.as_str(), "xhigh");
}
#[test]
fn test_selectable_models_for_agent_kinds_uses_provider_order() {
let agent_kinds = [AgentKind::Codex, AgentKind::Antigravity];
let selectable_models = selectable_models_for_agent_kinds(&agent_kinds);
assert_eq!(
selectable_models,
vec![
AgentModel::Gpt55,
AgentModel::Gpt54Mini,
AgentModel::Gpt53CodexSpark,
AgentModel::Gemini31ProPreview,
AgentModel::Gemini35Flash,
AgentModel::Gemini31FlashLitePreview,
AgentModel::Gemini3FlashPreview,
]
);
}
#[test]
fn test_selectable_models_for_agent_kinds_deduplicates_shared_models() {
let agent_kinds = [AgentKind::Gemini, AgentKind::Antigravity];
let selectable_models = selectable_models_for_agent_kinds(&agent_kinds);
assert_eq!(
selectable_models,
vec![
AgentModel::Gemini31ProPreview,
AgentModel::Gemini35Flash,
AgentModel::Gemini31FlashLitePreview,
AgentModel::Gemini3FlashPreview,
]
);
}
#[test]
fn test_resolve_model_for_available_agent_kinds_prefers_available_fallback() {
let unavailable_model = AgentModel::ClaudeOpus48;
let available_agent_kinds = [AgentKind::Codex, AgentKind::Antigravity];
let fallback_model = AgentModel::Gpt55;
let resolved_model = resolve_model_for_available_agent_kinds(
unavailable_model,
&available_agent_kinds,
fallback_model,
);
assert_eq!(resolved_model, AgentModel::Gpt55);
}
#[test]
fn test_resolve_model_for_available_agent_kinds_uses_first_available_default() {
let unavailable_model = AgentModel::ClaudeOpus48;
let available_agent_kinds = [AgentKind::Codex, AgentKind::Antigravity];
let unavailable_fallback_model = AgentModel::ClaudeSonnet5;
let resolved_model = resolve_model_for_available_agent_kinds(
unavailable_model,
&available_agent_kinds,
unavailable_fallback_model,
);
assert_eq!(resolved_model, AgentKind::Codex.default_model());
}
#[test]
fn test_resolve_agent_selection_for_model_preserves_preferred_shared_provider() {
let model = AgentModel::Gemini35Flash;
let available_agent_kinds = [AgentKind::Gemini, AgentKind::Antigravity];
let resolved_selection = resolve_agent_selection_for_model(
model,
AgentKind::Antigravity,
&available_agent_kinds,
);
assert_eq!(
resolved_selection,
AgentSelection::new(AgentKind::Antigravity, model)
);
}
#[test]
fn test_resolve_agent_selection_for_model_uses_available_provider_order() {
let model = AgentModel::Gemini35Flash;
let available_agent_kinds = [AgentKind::Gemini, AgentKind::Antigravity];
let resolved_selection =
resolve_agent_selection_for_model(model, AgentKind::Codex, &available_agent_kinds);
assert_eq!(
resolved_selection,
AgentSelection::new(AgentKind::Gemini, model)
);
}
#[test]
fn test_resolve_prompt_model_agent_kind_prefers_current_agent() {
let available_agent_kinds = [AgentKind::Antigravity, AgentKind::Codex];
let resolved_agent_kind =
resolve_prompt_model_agent_kind(AgentKind::Codex, &available_agent_kinds);
assert_eq!(resolved_agent_kind, Some(AgentKind::Codex));
}
#[test]
fn test_resolve_prompt_model_agent_kind_uses_first_available_agent() {
let available_agent_kinds = [AgentKind::Antigravity, AgentKind::Codex];
let resolved_agent_kind =
resolve_prompt_model_agent_kind(AgentKind::Claude, &available_agent_kinds);
assert_eq!(resolved_agent_kind, Some(AgentKind::Antigravity));
}
}