agent_ui: Handle legacy agent enum variants during deserialization (#51125)
Add custom `Deserialize` implementations for `AgentType` and `ExternalAgent` to map old built-in variant names to current custom agent names, while still accepting current serialized formats. Release Notes: - N/A
This commit is contained in:
1 parent
c429048333
commit
5359ccf2f2
2 files changed
+227
-2
No files matched your search
@@ -400,7 +400,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,
|
||||
@@ -410,6 +410,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<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
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)
|
||||
@@ -5310,4 +5367,77 @@ mod tests {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_legacy_agent_type_variants() {
|
||||
assert_eq!(
|
||||
serde_json::from_str::<AgentType>(r#""ClaudeAgent""#).unwrap(),
|
||||
AgentType::Custom {
|
||||
name: CLAUDE_AGENT_NAME.into(),
|
||||
},
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<AgentType>(r#""ClaudeCode""#).unwrap(),
|
||||
AgentType::Custom {
|
||||
name: CLAUDE_AGENT_NAME.into(),
|
||||
},
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<AgentType>(r#""Codex""#).unwrap(),
|
||||
AgentType::Custom {
|
||||
name: CODEX_NAME.into(),
|
||||
},
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<AgentType>(r#""Gemini""#).unwrap(),
|
||||
AgentType::Custom {
|
||||
name: GEMINI_NAME.into(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_current_agent_type_variants() {
|
||||
assert_eq!(
|
||||
serde_json::from_str::<AgentType>(r#""NativeAgent""#).unwrap(),
|
||||
AgentType::NativeAgent,
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<AgentType>(r#""TextThread""#).unwrap(),
|
||||
AgentType::TextThread,
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<AgentType>(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(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -212,13 +212,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<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
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,
|
||||
@@ -685,4 +742,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::<ExternalAgent>(r#""claude_code""#).unwrap(),
|
||||
ExternalAgent::Custom {
|
||||
name: CLAUDE_AGENT_NAME.into(),
|
||||
},
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ExternalAgent>(r#""codex""#).unwrap(),
|
||||
ExternalAgent::Custom {
|
||||
name: CODEX_NAME.into(),
|
||||
},
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ExternalAgent>(r#""gemini""#).unwrap(),
|
||||
ExternalAgent::Custom {
|
||||
name: GEMINI_NAME.into(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_current_external_agent_variants() {
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ExternalAgent>(r#""native_agent""#).unwrap(),
|
||||
ExternalAgent::NativeAgent,
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ExternalAgent>(r#"{"custom":{"name":"my-agent"}}"#).unwrap(),
|
||||
ExternalAgent::Custom {
|
||||
name: "my-agent".into(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user