Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 8 additions & 32 deletions agent-client-protocol-schema/src/v2/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Implementation>,
/// 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.
Expand All @@ -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,
}
}
Expand All @@ -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<Implementation>) -> 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.
Expand Down Expand Up @@ -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<AuthMethod>,
/// 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<Implementation>,
/// 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.
Expand All @@ -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,
}
}
Expand All @@ -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<Implementation>) -> 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.
Expand Down
91 changes: 69 additions & 22 deletions agent-client-protocol-schema/src/v2/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()?,
})
}
Expand All @@ -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()?,
})
}
Expand All @@ -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()?,
})
}
Expand All @@ -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()?,
})
}
Expand Down Expand Up @@ -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]
Expand All @@ -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]
Expand All @@ -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);
}

Expand Down Expand Up @@ -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!(
Expand Down
15 changes: 5 additions & 10 deletions docs/protocol/v2/draft/initialization.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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"
Expand All @@ -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
{
Expand All @@ -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"
Expand Down Expand Up @@ -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:

<ParamField path="name" type="string">
Intended for programmatic or logical use, but can be used as a display name
Expand All @@ -247,10 +246,6 @@ Both Clients and Agents **SHOULD** provide information about their implementatio
debugging or metrics purposes.
</ParamField>

<Info>
Note: in future versions of the protocol, this information will be required.
</Info>

---

Once the connection is initialized, you're ready to [create a session](/protocol/v2/draft/session-setup) and begin the conversation with the Agent.
16 changes: 5 additions & 11 deletions docs/protocol/v2/draft/schema.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -282,11 +282,8 @@ See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/d
- Default: `{"auth":{}}`

</ResponseField>
<ResponseField name="clientInfo" type={<><span><a href="#implementation">Implementation</a></span><span> | null</span></>} >
Information about the Client name and version sent to the Agent.

Note: in future versions of the protocol, this will be required.

<ResponseField name="info" type={<a href="#implementation">Implementation</a>} required>
Information about the implementation sending this initialize request.
</ResponseField>
<ResponseField name="protocolVersion" type={<a href="#protocolversion">ProtocolVersion</a>} required>
The latest protocol version supported by the client.
Expand All @@ -311,12 +308,6 @@ these keys.

See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)

</ResponseField>
<ResponseField name="agentInfo" type={<><span><a href="#implementation">Implementation</a></span><span> | null</span></>} >
Information about the Agent name and version sent to the Client.

Note: in future versions of the protocol, this will be required.

</ResponseField>
<ResponseField name="authMethods" type={<a href="#authmethod">AuthMethod[]</a>} >
Authentication methods supported by the agent.
Expand All @@ -330,6 +321,9 @@ Note: in future versions of the protocol, this will be required.
- Default: `{"auth":{}}`

</ResponseField>
<ResponseField name="info" type={<a href="#implementation">Implementation</a>} required>
Information about the implementation sending this initialize response.
</ResponseField>
<ResponseField name="protocolVersion" type={<a href="#protocolversion">ProtocolVersion</a>} required>
The protocol version the client specified if supported by the agent,
or the latest protocol version supported by the agent.
Expand Down
Loading