diff --git a/agent-client-protocol-schema/src/v2/agent.rs b/agent-client-protocol-schema/src/v2/agent.rs index c1913a3e2..0e46dbc9b 100644 --- a/agent-client-protocol-schema/src/v2/agent.rs +++ b/agent-client-protocol-schema/src/v2/agent.rs @@ -58,13 +58,8 @@ pub struct InitializeRequest { /// Capabilities supported by the client. #[serde(default)] pub capabilities: ClientCapabilities, - /// Information about the Client name and version sent to the Agent. - /// - /// Note: in future versions of the protocol, this will be required. - #[serde_as(deserialize_as = "DefaultOnError")] - #[schemars(extend("x-deserialize-default-on-error" = true))] - #[serde(default)] - pub client_info: Option, + /// Information about the implementation sending this initialize request. + pub info: Implementation, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. @@ -77,11 +72,11 @@ pub struct InitializeRequest { impl InitializeRequest { /// Builds [`InitializeRequest`] with the required request fields set; optional fields start unset or empty. #[must_use] - pub fn new(protocol_version: ProtocolVersion) -> Self { + pub fn new(protocol_version: ProtocolVersion, info: Implementation) -> Self { Self { protocol_version, capabilities: ClientCapabilities::default(), - client_info: None, + info, meta: None, } } @@ -93,13 +88,6 @@ impl InitializeRequest { self } - /// Information about the Client name and version sent to the Agent. - #[must_use] - pub fn client_info(mut self, client_info: impl IntoOption) -> Self { - self.client_info = client_info.into_option(); - self - } - /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. @@ -137,13 +125,8 @@ pub struct InitializeResponse { #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))] #[serde(default)] pub auth_methods: Vec, - /// Information about the Agent name and version sent to the Client. - /// - /// Note: in future versions of the protocol, this will be required. - #[serde_as(deserialize_as = "DefaultOnError")] - #[schemars(extend("x-deserialize-default-on-error" = true))] - #[serde(default)] - pub agent_info: Option, + /// Information about the implementation sending this initialize response. + pub info: Implementation, /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. @@ -156,12 +139,12 @@ pub struct InitializeResponse { impl InitializeResponse { /// Builds [`InitializeResponse`] with the required response fields set; optional fields start unset or empty. #[must_use] - pub fn new(protocol_version: ProtocolVersion) -> Self { + pub fn new(protocol_version: ProtocolVersion, info: Implementation) -> Self { Self { protocol_version, capabilities: AgentCapabilities::default(), auth_methods: vec![], - agent_info: None, + info, meta: None, } } @@ -180,13 +163,6 @@ impl InitializeResponse { self } - /// Information about the Agent name and version sent to the Client. - #[must_use] - pub fn agent_info(mut self, agent_info: impl IntoOption) -> Self { - self.agent_info = agent_info.into_option(); - self - } - /// The _meta property is reserved by ACP to allow clients and agents to attach additional /// metadata to their interactions. Implementations MUST NOT make assumptions about values at /// these keys. diff --git a/agent-client-protocol-schema/src/v2/conversion.rs b/agent-client-protocol-schema/src/v2/conversion.rs index fc3d165b9..a04c492b1 100644 --- a/agent-client-protocol-schema/src/v2/conversion.rs +++ b/agent-client-protocol-schema/src/v2/conversion.rs @@ -2697,13 +2697,13 @@ impl IntoV1 for super::InitializeRequest { let Self { protocol_version, capabilities, - client_info, + info, meta, } = self; Ok(crate::v1::InitializeRequest { protocol_version: protocol_version.into_v1()?, client_capabilities: capabilities.into_v1()?, - client_info: into_v1_default_on_error(client_info), + client_info: Some(info.into_v1()?), meta: meta.into_v1()?, }) } @@ -2719,10 +2719,18 @@ impl IntoV2 for crate::v1::InitializeRequest { client_info, meta, } = self; + let info = match client_info { + Some(client_info) => client_info.into_v2()?, + None => { + return Err(ProtocolConversionError::new( + "v1 InitializeRequest without `clientInfo` cannot be represented in v2", + )); + } + }; Ok(super::InitializeRequest { protocol_version: protocol_version.into_v2()?, capabilities: client_capabilities.into_v2()?, - client_info: into_v2_default_on_error(client_info), + info, meta: meta.into_v2()?, }) } @@ -2736,14 +2744,14 @@ impl IntoV1 for super::InitializeResponse { protocol_version, capabilities: agent_capabilities, auth_methods, - agent_info, + info, meta, } = self; Ok(crate::v1::InitializeResponse { protocol_version: protocol_version.into_v1()?, agent_capabilities: agent_capabilities.into_v1()?, auth_methods: into_v1_vec_skip_errors(auth_methods), - agent_info: into_v1_default_on_error(agent_info), + agent_info: Some(info.into_v1()?), meta: meta.into_v1()?, }) } @@ -2760,11 +2768,19 @@ impl IntoV2 for crate::v1::InitializeResponse { agent_info, meta, } = self; + let info = match agent_info { + Some(agent_info) => agent_info.into_v2()?, + None => { + return Err(ProtocolConversionError::new( + "v1 InitializeResponse without `agentInfo` cannot be represented in v2", + )); + } + }; Ok(super::InitializeResponse { protocol_version: protocol_version.into_v2()?, capabilities: agent_capabilities.into_v2()?, auth_methods: into_v2_vec_skip_errors(auth_methods), - agent_info: into_v2_default_on_error(agent_info), + info, meta: meta.into_v2()?, }) } @@ -9049,20 +9065,41 @@ mod tests { #[test] fn converts_v2_initialize_request_to_v1_without_serde() { - let request = v2::InitializeRequest::new(ProtocolVersion::V2); + let request = v2::InitializeRequest::new( + ProtocolVersion::V2, + v2::Implementation::new("test-client", "1.0.0"), + ); let converted: v1::InitializeRequest = v2_to_v1(request).unwrap(); assert_eq!(converted.protocol_version, ProtocolVersion::V2); + assert_eq!( + converted + .client_info + .as_ref() + .map(|info| info.name.as_str()), + Some("test-client") + ); } #[test] - fn converts_v1_initialize_request_to_v2_without_serde() { + fn v1_initialize_request_without_client_info_does_not_convert_to_v2() { let request = v1::InitializeRequest::new(ProtocolVersion::V1); - let converted: v2::InitializeRequest = v1_to_v2(request).unwrap(); + assert_v1_to_v2_error( + request, + "v1 InitializeRequest without `clientInfo` cannot be represented in v2", + ); + } + + #[test] + fn v1_initialize_response_without_agent_info_does_not_convert_to_v2() { + let response = v1::InitializeResponse::new(ProtocolVersion::V1); - assert_eq!(converted.protocol_version, ProtocolVersion::V1); + assert_v1_to_v2_error( + response, + "v1 InitializeResponse without `agentInfo` cannot be represented in v2", + ); } #[test] @@ -9086,9 +9123,13 @@ mod tests { let converted: v2::InitializeRequest = v1_to_v2(request).expect("v1 -> v2 conversion failed"); let converted_capabilities = - serde_json::to_value(converted.capabilities).expect("v2 serialize"); + serde_json::to_value(&converted.capabilities).expect("v2 serialize"); assert_eq!(converted_capabilities.get("fs"), None); assert_eq!(converted_capabilities.get("terminal"), None); + let converted_json = serde_json::to_value(&converted).expect("v2 serialize"); + assert_eq!(converted_json.get("clientInfo"), None); + assert_eq!(converted_json.get("implementation"), None); + assert!(converted_json.get("info").is_some()); } #[test] @@ -9101,6 +9142,9 @@ mod tests { let converted_json = serde_json::to_value(&converted).expect("v2 serialize"); assert_eq!(converted_json.get("agentCapabilities"), None); assert!(converted_json.get("capabilities").is_some()); + assert_eq!(converted_json.get("agentInfo"), None); + assert_eq!(converted_json.get("implementation"), None); + assert!(converted_json.get("info").is_some()); assert_eq!(converted_json.pointer("/capabilities/loadSession"), None); } @@ -9918,17 +9962,20 @@ mod tests { #[test] fn v2_collection_conversion_skips_items_like_v1_vec_skip_error() { - let response = v2::InitializeResponse::new(ProtocolVersion::V2) - .capabilities(v2::AgentCapabilities::new().session(v2::SessionCapabilities::new())) - .auth_methods(vec![ - v2::AuthMethod::Other(v2::OtherAuthMethod::new( - "_oauth", - "oauth", - "OAuth", - BTreeMap::default(), - )), - v2::AuthMethod::Agent(v2::AuthMethodAgent::new("agent", "Agent")), - ]); + let response = v2::InitializeResponse::new( + ProtocolVersion::V2, + v2::Implementation::new("test-agent", "2.0.0"), + ) + .capabilities(v2::AgentCapabilities::new().session(v2::SessionCapabilities::new())) + .auth_methods(vec![ + v2::AuthMethod::Other(v2::OtherAuthMethod::new( + "_oauth", + "oauth", + "OAuth", + BTreeMap::default(), + )), + v2::AuthMethod::Agent(v2::AuthMethodAgent::new("agent", "Agent")), + ]); let converted: v1::InitializeResponse = v2_to_v1(response).unwrap(); assert_eq!(converted.auth_methods.len(), 1); assert!(matches!( diff --git a/docs/protocol/v2/draft/initialization.mdx b/docs/protocol/v2/draft/initialization.mdx index 2e6bf3b79..3a9a2c625 100644 --- a/docs/protocol/v2/draft/initialization.mdx +++ b/docs/protocol/v2/draft/initialization.mdx @@ -27,8 +27,7 @@ Before a Session can be created, Clients **MUST** initialize the connection by c - The latest [protocol version](#protocol-version) supported - The [capabilities](#client-capabilities) supported - -They **SHOULD** also provide a name and version to the Agent. +- The [implementation information](#implementation-information) for the Client ```json { @@ -38,7 +37,7 @@ They **SHOULD** also provide a name and version to the Agent. "params": { "protocolVersion": 2, "capabilities": {}, - "clientInfo": { + "info": { "name": "my-client", "title": "My Client", "version": "1.0.0" @@ -47,7 +46,7 @@ They **SHOULD** also provide a name and version to the Agent. } ``` -The Agent **MUST** respond with the chosen [protocol version](#protocol-version) and the [capabilities](#agent-capabilities) it supports. It **SHOULD** also provide a name and version to the Client as well: +The Agent **MUST** respond with the chosen [protocol version](#protocol-version), the [capabilities](#agent-capabilities) it supports, and its [implementation information](#implementation-information): ```json { @@ -69,7 +68,7 @@ The Agent **MUST** respond with the chosen [protocol version](#protocol-version) "load": {} } }, - "agentInfo": { + "info": { "name": "my-agent", "title": "My Agent", "version": "1.0.0" @@ -230,7 +229,7 @@ Optionally, they **MAY** support richer types of [content](/protocol/v2/draft/co ## Implementation Information -Both Clients and Agents **SHOULD** provide information about their implementation in the `clientInfo` and `agentInfo` fields respectively. Both take the following three fields: +Both Clients and Agents **MUST** provide information about their implementation in the `info` field. It takes the following three fields: Intended for programmatic or logical use, but can be used as a display name @@ -247,10 +246,6 @@ Both Clients and Agents **SHOULD** provide information about their implementatio debugging or metrics purposes. - - Note: in future versions of the protocol, this information will be required. - - --- Once the connection is initialized, you're ready to [create a session](/protocol/v2/draft/session-setup) and begin the conversation with the Agent. diff --git a/docs/protocol/v2/draft/schema.mdx b/docs/protocol/v2/draft/schema.mdx index babf916df..e2cd64bfa 100644 --- a/docs/protocol/v2/draft/schema.mdx +++ b/docs/protocol/v2/draft/schema.mdx @@ -282,11 +282,8 @@ See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/d - Default: `{"auth":{}}` -Implementation | null} > - Information about the Client name and version sent to the Agent. - -Note: in future versions of the protocol, this will be required. - +Implementation} required> + Information about the implementation sending this initialize request. ProtocolVersion} required> The latest protocol version supported by the client. @@ -311,12 +308,6 @@ these keys. See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) - -Implementation | null} > - Information about the Agent name and version sent to the Client. - -Note: in future versions of the protocol, this will be required. - AuthMethod[]} > Authentication methods supported by the agent. @@ -330,6 +321,9 @@ Note: in future versions of the protocol, this will be required. - Default: `{"auth":{}}` +Implementation} required> + Information about the implementation sending this initialize response. + ProtocolVersion} required> The protocol version the client specified if supported by the agent, or the latest protocol version supported by the agent. diff --git a/docs/protocol/v2/initialization.mdx b/docs/protocol/v2/initialization.mdx index ee3ed263d..68e46bb29 100644 --- a/docs/protocol/v2/initialization.mdx +++ b/docs/protocol/v2/initialization.mdx @@ -27,8 +27,7 @@ Before a Session can be created, Clients **MUST** initialize the connection by c - The latest [protocol version](#protocol-version) supported - The [capabilities](#client-capabilities) supported - -They **SHOULD** also provide a name and version to the Agent. +- The [implementation information](#implementation-information) for the Client ```json { @@ -38,7 +37,7 @@ They **SHOULD** also provide a name and version to the Agent. "params": { "protocolVersion": 2, "capabilities": {}, - "clientInfo": { + "info": { "name": "my-client", "title": "My Client", "version": "1.0.0" @@ -47,7 +46,7 @@ They **SHOULD** also provide a name and version to the Agent. } ``` -The Agent **MUST** respond with the chosen [protocol version](#protocol-version) and the [capabilities](#agent-capabilities) it supports. It **SHOULD** also provide a name and version to the Client as well: +The Agent **MUST** respond with the chosen [protocol version](#protocol-version), the [capabilities](#agent-capabilities) it supports, and its [implementation information](#implementation-information): ```json { @@ -69,7 +68,7 @@ The Agent **MUST** respond with the chosen [protocol version](#protocol-version) "load": {} } }, - "agentInfo": { + "info": { "name": "my-agent", "title": "My Agent", "version": "1.0.0" @@ -224,7 +223,7 @@ Optionally, they **MAY** support richer types of [content](/protocol/v2/content) ## Implementation Information -Both Clients and Agents **SHOULD** provide information about their implementation in the `clientInfo` and `agentInfo` fields respectively. Both take the following three fields: +Both Clients and Agents **MUST** provide information about their implementation in the `info` field. It takes the following three fields: Intended for programmatic or logical use, but can be used as a display name @@ -241,10 +240,6 @@ Both Clients and Agents **SHOULD** provide information about their implementatio debugging or metrics purposes. - - Note: in future versions of the protocol, this information will be required. - - --- Once the connection is initialized, you're ready to [create a session](/protocol/v2/session-setup) and begin the conversation with the Agent. diff --git a/docs/protocol/v2/schema.mdx b/docs/protocol/v2/schema.mdx index b8516e9c2..325486ec6 100644 --- a/docs/protocol/v2/schema.mdx +++ b/docs/protocol/v2/schema.mdx @@ -108,11 +108,8 @@ See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/e - Default: `{}` -Implementation | null} > - Information about the Client name and version sent to the Agent. - -Note: in future versions of the protocol, this will be required. - +Implementation} required> + Information about the implementation sending this initialize request. ProtocolVersion} required> The latest protocol version supported by the client. @@ -137,12 +134,6 @@ these keys. See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) - -Implementation | null} > - Information about the Agent name and version sent to the Client. - -Note: in future versions of the protocol, this will be required. - AuthMethod[]} > Authentication methods supported by the agent. @@ -156,6 +147,9 @@ Note: in future versions of the protocol, this will be required. - Default: `{"auth":{}}` +Implementation} required> + Information about the implementation sending this initialize response. + ProtocolVersion} required> The protocol version the client specified if supported by the agent, or the latest protocol version supported by the agent. diff --git a/schema-generator/src/main.rs b/schema-generator/src/main.rs index 70175aeaf..f8f930d4e 100644 --- a/schema-generator/src/main.rs +++ b/schema-generator/src/main.rs @@ -316,9 +316,34 @@ mod schema_annotation_tests { fn generated_schema_includes_tolerant_deserialization_extensions() { let schema = root_schema_value(); - let client_info = property_schema(&schema, "InitializeRequest", "clientInfo"); - assert_bool_extension(client_info, DEFAULT_ON_ERROR_EXTENSION); - assert_no_extension(client_info, SKIP_INVALID_ITEMS_EXTENSION); + #[cfg(not(feature = "unstable_protocol_v2"))] + { + let client_info = property_schema(&schema, "InitializeRequest", "clientInfo"); + assert_bool_extension(client_info, DEFAULT_ON_ERROR_EXTENSION); + assert_no_extension(client_info, SKIP_INVALID_ITEMS_EXTENSION); + } + + #[cfg(feature = "unstable_protocol_v2")] + { + let request = def_schema(&schema, "InitializeRequest"); + assert!( + request + .pointer("/required") + .and_then(Value::as_array) + .is_some_and(|required| required.iter().any(|field| field == "info")) + ); + let info = property_schema(&schema, "InitializeRequest", "info"); + assert_no_extension(info, DEFAULT_ON_ERROR_EXTENSION); + assert_no_extension(info, SKIP_INVALID_ITEMS_EXTENSION); + + let response = def_schema(&schema, "InitializeResponse"); + assert!( + response + .pointer("/required") + .and_then(Value::as_array) + .is_some_and(|required| required.iter().any(|field| field == "info")) + ); + } let auth_methods = property_schema(&schema, "InitializeResponse", "authMethods"); assert_bool_extension(auth_methods, DEFAULT_ON_ERROR_EXTENSION); diff --git a/schema/v2/schema.json b/schema/v2/schema.json index dce911c07..f60d87e95 100644 --- a/schema/v2/schema.json +++ b/schema/v2/schema.json @@ -1539,17 +1539,13 @@ "x-deserialize-default-on-error": true, "x-deserialize-skip-invalid-items": true }, - "agentInfo": { - "description": "Information about the Agent name and version sent to the Client.\n\nNote: in future versions of the protocol, this will be required.", - "anyOf": [ + "info": { + "description": "Information about the implementation sending this initialize response.", + "allOf": [ { "$ref": "#/$defs/Implementation" - }, - { - "type": "null" } - ], - "x-deserialize-default-on-error": true + ] }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)", @@ -1557,7 +1553,7 @@ "additionalProperties": true } }, - "required": ["protocolVersion"], + "required": ["protocolVersion", "info"], "x-side": "agent", "x-method": "initialize" }, @@ -3986,17 +3982,13 @@ } ] }, - "clientInfo": { - "description": "Information about the Client name and version sent to the Agent.\n\nNote: in future versions of the protocol, this will be required.", - "anyOf": [ + "info": { + "description": "Information about the implementation sending this initialize request.", + "allOf": [ { "$ref": "#/$defs/Implementation" - }, - { - "type": "null" } - ], - "x-deserialize-default-on-error": true + ] }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)", @@ -4004,7 +3996,7 @@ "additionalProperties": true } }, - "required": ["protocolVersion"], + "required": ["protocolVersion", "info"], "x-side": "agent", "x-method": "initialize" }, diff --git a/schema/v2/schema.unstable.json b/schema/v2/schema.unstable.json index 741da3a5b..51f5312bd 100644 --- a/schema/v2/schema.unstable.json +++ b/schema/v2/schema.unstable.json @@ -2540,17 +2540,13 @@ "x-deserialize-default-on-error": true, "x-deserialize-skip-invalid-items": true }, - "agentInfo": { - "description": "Information about the Agent name and version sent to the Client.\n\nNote: in future versions of the protocol, this will be required.", - "anyOf": [ + "info": { + "description": "Information about the implementation sending this initialize response.", + "allOf": [ { "$ref": "#/$defs/Implementation" - }, - { - "type": "null" } - ], - "x-deserialize-default-on-error": true + ] }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)", @@ -2558,7 +2554,7 @@ "additionalProperties": true } }, - "required": ["protocolVersion"], + "required": ["protocolVersion", "info"], "x-side": "agent", "x-method": "initialize" }, @@ -6598,17 +6594,13 @@ } ] }, - "clientInfo": { - "description": "Information about the Client name and version sent to the Agent.\n\nNote: in future versions of the protocol, this will be required.", - "anyOf": [ + "info": { + "description": "Information about the implementation sending this initialize request.", + "allOf": [ { "$ref": "#/$defs/Implementation" - }, - { - "type": "null" } - ], - "x-deserialize-default-on-error": true + ] }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)", @@ -6616,7 +6608,7 @@ "additionalProperties": true } }, - "required": ["protocolVersion"], + "required": ["protocolVersion", "info"], "x-side": "agent", "x-method": "initialize" },