diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 7e3dc5cb07bd8c777c8edb8edac9472d1bb81acb..e559607edc5b677cb6a0428febd30ebd89b5d2b1 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -374,7 +374,7 @@ enum WhichFontSize { } // TODO unify this with ExternalAgent -#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, PartialEq, Serialize)] pub enum AgentType { #[default] NativeAgent, @@ -384,6 +384,63 @@ pub enum AgentType { }, } +// Custom impl handles legacy variant names from before the built-in agents were moved to +// the registry: "ClaudeAgent" -> Custom { name: "claude-acp" }, "Codex" -> Custom { name: +// "codex-acp" }, "Gemini" -> Custom { name: "gemini" }. +// Can be removed at some point in the future and go back to #[derive(Deserialize)]. +impl<'de> Deserialize<'de> for AgentType { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = serde_json::Value::deserialize(deserializer)?; + + if let Some(s) = value.as_str() { + return match s { + "NativeAgent" => Ok(Self::NativeAgent), + "TextThread" => Ok(Self::TextThread), + "ClaudeAgent" | "ClaudeCode" => Ok(Self::Custom { + name: CLAUDE_AGENT_NAME.into(), + }), + "Codex" => Ok(Self::Custom { + name: CODEX_NAME.into(), + }), + "Gemini" => Ok(Self::Custom { + name: GEMINI_NAME.into(), + }), + other => Err(serde::de::Error::unknown_variant( + other, + &[ + "NativeAgent", + "TextThread", + "Custom", + "ClaudeAgent", + "ClaudeCode", + "Codex", + "Gemini", + ], + )), + }; + } + + if let Some(obj) = value.as_object() { + if let Some(inner) = obj.get("Custom") { + #[derive(Deserialize)] + struct CustomFields { + name: SharedString, + } + let fields: CustomFields = + serde_json::from_value(inner.clone()).map_err(serde::de::Error::custom)?; + return Ok(Self::Custom { name: fields.name }); + } + } + + Err(serde::de::Error::custom( + "expected a string variant or {\"Custom\": {\"name\": ...}}", + )) + } +} + impl AgentType { pub fn is_native(&self) -> bool { matches!(self, Self::NativeAgent) @@ -4869,4 +4926,77 @@ mod tests { ); }); } + + #[test] + fn test_deserialize_legacy_agent_type_variants() { + assert_eq!( + serde_json::from_str::(r#""ClaudeAgent""#).unwrap(), + AgentType::Custom { + name: CLAUDE_AGENT_NAME.into(), + }, + ); + assert_eq!( + serde_json::from_str::(r#""ClaudeCode""#).unwrap(), + AgentType::Custom { + name: CLAUDE_AGENT_NAME.into(), + }, + ); + assert_eq!( + serde_json::from_str::(r#""Codex""#).unwrap(), + AgentType::Custom { + name: CODEX_NAME.into(), + }, + ); + assert_eq!( + serde_json::from_str::(r#""Gemini""#).unwrap(), + AgentType::Custom { + name: GEMINI_NAME.into(), + }, + ); + } + + #[test] + fn test_deserialize_current_agent_type_variants() { + assert_eq!( + serde_json::from_str::(r#""NativeAgent""#).unwrap(), + AgentType::NativeAgent, + ); + assert_eq!( + serde_json::from_str::(r#""TextThread""#).unwrap(), + AgentType::TextThread, + ); + assert_eq!( + serde_json::from_str::(r#"{"Custom":{"name":"my-agent"}}"#).unwrap(), + AgentType::Custom { + name: "my-agent".into(), + }, + ); + } + + #[test] + fn test_deserialize_legacy_serialized_panel() { + let json = serde_json::json!({ + "width": 300.0, + "selected_agent": "ClaudeAgent", + "last_active_thread": { + "session_id": "test-session", + "agent_type": "Codex", + }, + }); + + let panel: SerializedAgentPanel = serde_json::from_value(json).unwrap(); + assert_eq!( + panel.selected_agent, + Some(AgentType::Custom { + name: CLAUDE_AGENT_NAME.into(), + }), + ); + let thread = panel.last_active_thread.unwrap(); + assert_eq!( + thread.agent_type, + AgentType::Custom { + name: CODEX_NAME.into(), + }, + ); + } } diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index 58a8edca779daa50862549058a0068e2ddb7c5bf..83d5d61865dbdce139346f662bb24e99893f3248 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -204,13 +204,70 @@ pub struct NewNativeAgentThreadFromSummary { } // TODO unify this with AgentType -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Serialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ExternalAgent { NativeAgent, Custom { name: SharedString }, } +// Custom impl handles legacy variant names from before the built-in agents were moved to +// the registry: "claude_code" -> Custom { name: "claude-acp" }, "codex" -> Custom { name: +// "codex-acp" }, "gemini" -> Custom { name: "gemini" }. +// Can be removed at some point in the future and go back to #[derive(Deserialize)]. +impl<'de> serde::Deserialize<'de> for ExternalAgent { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use project::agent_server_store::{CLAUDE_AGENT_NAME, CODEX_NAME, GEMINI_NAME}; + + let value = serde_json::Value::deserialize(deserializer)?; + + if let Some(s) = value.as_str() { + return match s { + "native_agent" => Ok(Self::NativeAgent), + "claude_code" | "claude_agent" => Ok(Self::Custom { + name: CLAUDE_AGENT_NAME.into(), + }), + "codex" => Ok(Self::Custom { + name: CODEX_NAME.into(), + }), + "gemini" => Ok(Self::Custom { + name: GEMINI_NAME.into(), + }), + other => Err(serde::de::Error::unknown_variant( + other, + &[ + "native_agent", + "custom", + "claude_agent", + "claude_code", + "codex", + "gemini", + ], + )), + }; + } + + if let Some(obj) = value.as_object() { + if let Some(inner) = obj.get("custom") { + #[derive(serde::Deserialize)] + struct CustomFields { + name: SharedString, + } + let fields: CustomFields = + serde_json::from_value(inner.clone()).map_err(serde::de::Error::custom)?; + return Ok(Self::Custom { name: fields.name }); + } + } + + Err(serde::de::Error::custom( + "expected a string variant or {\"custom\": {\"name\": ...}}", + )) + } +} + impl ExternalAgent { pub fn server( &self, @@ -667,4 +724,42 @@ mod tests { ); }); } + + #[test] + fn test_deserialize_legacy_external_agent_variants() { + use project::agent_server_store::{CLAUDE_AGENT_NAME, CODEX_NAME, GEMINI_NAME}; + + assert_eq!( + serde_json::from_str::(r#""claude_code""#).unwrap(), + ExternalAgent::Custom { + name: CLAUDE_AGENT_NAME.into(), + }, + ); + assert_eq!( + serde_json::from_str::(r#""codex""#).unwrap(), + ExternalAgent::Custom { + name: CODEX_NAME.into(), + }, + ); + assert_eq!( + serde_json::from_str::(r#""gemini""#).unwrap(), + ExternalAgent::Custom { + name: GEMINI_NAME.into(), + }, + ); + } + + #[test] + fn test_deserialize_current_external_agent_variants() { + assert_eq!( + serde_json::from_str::(r#""native_agent""#).unwrap(), + ExternalAgent::NativeAgent, + ); + assert_eq!( + serde_json::from_str::(r#"{"custom":{"name":"my-agent"}}"#).unwrap(), + ExternalAgent::Custom { + name: "my-agent".into(), + }, + ); + } }