diff --git a/agent-client-protocol-schema/src/v2/agent.rs b/agent-client-protocol-schema/src/v2/agent.rs index 281b13590..8d4523af0 100644 --- a/agent-client-protocol-schema/src/v2/agent.rs +++ b/agent-client-protocol-schema/src/v2/agent.rs @@ -2778,8 +2778,8 @@ impl SetSessionConfigOptionRequest { #[non_exhaustive] pub struct SetSessionConfigOptionResponse { /// The full set of configuration options and their current values. - #[serde_as(deserialize_as = "DefaultOnError>")] - #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))] + #[serde_as(deserialize_as = "VecSkipError<_>")] + #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-skip-invalid-items" = true)))] pub config_options: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. diff --git a/agent-client-protocol-schema/src/v2/client.rs b/agent-client-protocol-schema/src/v2/client.rs index bd83dc7cd..9a00dde82 100644 --- a/agent-client-protocol-schema/src/v2/client.rs +++ b/agent-client-protocol-schema/src/v2/client.rs @@ -1338,8 +1338,8 @@ fn other_session_update_schema(schema: &mut Schema) { #[non_exhaustive] pub struct ConfigOptionUpdate { /// The full set of configuration options and their current values. - #[serde_as(deserialize_as = "DefaultOnError>")] - #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))] + #[serde_as(deserialize_as = "VecSkipError<_>")] + #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-skip-invalid-items" = true)))] pub config_options: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. @@ -2271,8 +2271,8 @@ impl MessageId { #[non_exhaustive] pub struct AvailableCommandsUpdate { /// Commands the agent can execute. - #[serde_as(deserialize_as = "DefaultOnError>")] - #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))] + #[serde_as(deserialize_as = "VecSkipError<_>")] + #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-skip-invalid-items" = true)))] pub available_commands: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. @@ -3684,6 +3684,43 @@ impl AgentNotification { #[cfg(test)] mod tests { + #[test] + fn replacement_lists_reject_non_arrays_instead_of_clearing() { + use serde_json::json; + for (update, key) in [ + ("config_option_update", "configOptions"), + ("available_commands_update", "availableCommands"), + ] { + let empty = json!({ "sessionUpdate": update, key: [] }); + serde_json::from_value::(empty).unwrap(); + for bad in [json!("oops"), json!(null), json!({})] { + let wire = json!({ "sessionUpdate": update, key: bad }); + assert!( + serde_json::from_value::(wire).is_err(), + "{update} {bad}" + ); + } + } + let plan = |entries: serde_json::Value| { + json!({ "sessionUpdate": "plan_update", + "plan": { "type": "items", "planId": "p", "entries": entries } }) + }; + serde_json::from_value::(plan(json!([]))).unwrap(); + assert!(serde_json::from_value::(plan(json!("oops"))).is_err()); + // Invalid items are still skipped. + let skipped = + plan(json!([42, { "content": "a", "priority": "high", "status": "pending" }])); + let super::SessionUpdate::PlanUpdate(update) = + serde_json::from_value::(skipped).unwrap() + else { + panic!("expected plan_update"); + }; + let crate::v2::PlanUpdateContent::Items(items) = update.plan else { + panic!("expected items"); + }; + assert_eq!(items.entries.len(), 1); + } + #[test] fn other_available_command_preserves_meta_verbatim() { for meta in [serde_json::json!({ "k": 1 }), serde_json::json!("bad")] { diff --git a/agent-client-protocol-schema/src/v2/plan.rs b/agent-client-protocol-schema/src/v2/plan.rs index d172502a2..56a195700 100644 --- a/agent-client-protocol-schema/src/v2/plan.rs +++ b/agent-client-protocol-schema/src/v2/plan.rs @@ -238,8 +238,8 @@ pub struct PlanItems { /// /// When updating an item-based plan, the agent must send a complete list of all entries /// with their current status. The client replaces that plan with each update. - #[serde_as(deserialize_as = "DefaultOnError>")] - #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))] + #[serde_as(deserialize_as = "VecSkipError<_>")] + #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-skip-invalid-items" = true)))] pub entries: Vec, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. diff --git a/schema/v2/schema.json b/schema/v2/schema.json index e5a7412ef..2009df5b6 100644 --- a/schema/v2/schema.json +++ b/schema/v2/schema.json @@ -4274,7 +4274,6 @@ "items": { "$ref": "#/$defs/SessionConfigOption" }, - "x-deserialize-default-on-error": true, "x-deserialize-skip-invalid-items": true }, "_meta": { @@ -5815,7 +5814,6 @@ "items": { "$ref": "#/$defs/PlanEntry" }, - "x-deserialize-default-on-error": true, "x-deserialize-skip-invalid-items": true }, "_meta": { @@ -5858,7 +5856,6 @@ "items": { "$ref": "#/$defs/AvailableCommand" }, - "x-deserialize-default-on-error": true, "x-deserialize-skip-invalid-items": true }, "_meta": { @@ -5880,7 +5877,6 @@ "items": { "$ref": "#/$defs/SessionConfigOption" }, - "x-deserialize-default-on-error": true, "x-deserialize-skip-invalid-items": true }, "_meta": { diff --git a/schema/v2/schema.unstable.json b/schema/v2/schema.unstable.json index c2a9e691b..2c8abe970 100644 --- a/schema/v2/schema.unstable.json +++ b/schema/v2/schema.unstable.json @@ -4757,7 +4757,6 @@ "items": { "$ref": "#/$defs/SessionConfigOption" }, - "x-deserialize-default-on-error": true, "x-deserialize-skip-invalid-items": true }, "_meta": { @@ -6556,7 +6555,6 @@ "items": { "$ref": "#/$defs/PlanEntry" }, - "x-deserialize-default-on-error": true, "x-deserialize-skip-invalid-items": true }, "_meta": { @@ -6671,7 +6669,6 @@ "items": { "$ref": "#/$defs/AvailableCommand" }, - "x-deserialize-default-on-error": true, "x-deserialize-skip-invalid-items": true }, "_meta": { @@ -6693,7 +6690,6 @@ "items": { "$ref": "#/$defs/SessionConfigOption" }, - "x-deserialize-default-on-error": true, "x-deserialize-skip-invalid-items": true }, "_meta": {