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
85 changes: 57 additions & 28 deletions agent-client-protocol-schema/src/v1/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3048,7 +3048,7 @@ pub struct McpServerAcp {
///
/// Providers MUST NOT reuse an ID for multiple ACP-transport MCP servers that are visible
/// on the same ACP connection.
pub id: McpServerAcpId,
pub server_id: McpServerAcpId,
/// 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 @@ -3068,7 +3068,7 @@ impl McpServerAcp {
pub fn new(name: impl Into<String>, id: impl Into<McpServerAcpId>) -> Self {
Self {
name: name.into(),
id: id.into(),
server_id: id.into(),
meta: None,
}
}
Expand Down Expand Up @@ -3591,6 +3591,27 @@ impl ProviderCurrentConfig {
}
}

/// **UNSTABLE**
///
/// This capability is not part of the spec yet, and may be removed or changed at any point.
///
/// Unique identifier for a configurable LLM provider.
#[cfg(feature = "unstable_llm_providers")]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
#[serde(transparent)]
#[from(Arc<str>, String, &'static str)]
#[non_exhaustive]
pub struct ProviderId(pub Arc<str>);

#[cfg(feature = "unstable_llm_providers")]
impl ProviderId {
/// Wraps a protocol string as a typed [`ProviderId`].
#[must_use]
pub fn new(id: impl Into<Arc<str>>) -> Self {
Self(id.into())
}
}

/// **UNSTABLE**
///
/// This capability is not part of the spec yet, and may be removed or changed at any point.
Expand All @@ -3604,13 +3625,13 @@ impl ProviderCurrentConfig {
#[non_exhaustive]
pub struct ProviderInfo {
/// Provider identifier, for example "main" or "openai".
pub id: String,
pub provider_id: ProviderId,
/// Supported protocol types for this provider.
#[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
#[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
pub supported: Vec<LlmProtocol>,
/// Whether this provider is mandatory and cannot be disabled via `providers/disable`.
/// If true, clients must not call `providers/disable` for this id.
/// If true, clients must not call `providers/disable` for this provider ID.
pub required: bool,
/// Current effective non-secret routing config.
/// Null or omitted means provider is disabled.
Expand All @@ -3635,13 +3656,13 @@ impl ProviderInfo {
/// Builds [`ProviderInfo`] with the required fields set; optional fields start unset or empty.
#[must_use]
pub fn new(
id: impl Into<String>,
provider_id: impl Into<ProviderId>,
supported: Vec<LlmProtocol>,
required: bool,
current: impl IntoOption<ProviderCurrentConfig>,
) -> Self {
Self {
id: id.into(),
provider_id: provider_id.into(),
supported,
required,
current: current.into_option(),
Expand Down Expand Up @@ -3764,7 +3785,7 @@ impl ListProvidersResponse {
///
/// Request parameters for `providers/set`.
///
/// Replaces the full configuration for one provider id.
/// Replaces the full configuration for one provider ID.
#[cfg(feature = "unstable_llm_providers")]
#[serde_as]
#[skip_serializing_none]
Expand All @@ -3773,8 +3794,8 @@ impl ListProvidersResponse {
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct SetProviderRequest {
/// Provider id to configure.
pub id: String,
/// Provider ID to configure.
pub provider_id: ProviderId,
/// Protocol type for this provider.
pub api_type: LlmProtocol,
/// Base URL for requests sent through this provider.
Expand All @@ -3801,9 +3822,13 @@ pub struct SetProviderRequest {
impl SetProviderRequest {
/// Builds [`SetProviderRequest`] with the required request fields set; optional fields start unset or empty.
#[must_use]
pub fn new(id: impl Into<String>, api_type: LlmProtocol, base_url: impl Into<String>) -> Self {
pub fn new(
provider_id: impl Into<ProviderId>,
api_type: LlmProtocol,
base_url: impl Into<String>,
) -> Self {
Self {
id: id.into(),
provider_id: provider_id.into(),
api_type,
base_url: base_url.into(),
headers: HashMap::new(),
Expand Down Expand Up @@ -3889,8 +3914,8 @@ impl SetProviderResponse {
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct DisableProviderRequest {
/// Provider id to disable.
pub id: String,
/// Provider ID to disable.
pub provider_id: ProviderId,
/// 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 @@ -3907,9 +3932,9 @@ pub struct DisableProviderRequest {
impl DisableProviderRequest {
/// Builds [`DisableProviderRequest`] with the required request fields set; optional fields start unset or empty.
#[must_use]
pub fn new(id: impl Into<String>) -> Self {
pub fn new(provider_id: impl Into<ProviderId>) -> Self {
Self {
id: id.into(),
provider_id: provider_id.into(),
meta: None,
}
}
Expand Down Expand Up @@ -5524,13 +5549,17 @@ mod test_serialization {
json!({
"type": "acp",
"name": "project-tools",
"id": "project-tools-id"
"serverId": "project-tools-id"
})
);

let deserialized: McpServer = serde_json::from_value(json).unwrap();
match deserialized {
McpServer::Acp(McpServerAcp { name, id, meta: _ }) => {
McpServer::Acp(McpServerAcp {
name,
server_id: id,
meta: _,
}) => {
assert_eq!(name, "project-tools");
assert_eq!(id, McpServerAcpId::new("project-tools-id"));
}
Expand Down Expand Up @@ -6416,7 +6445,7 @@ mod test_serialization {
assert_eq!(
json,
json!({
"id": "main",
"providerId": "main",
"supported": ["anthropic", "openai"],
"required": true,
"current": {
Expand All @@ -6427,7 +6456,7 @@ mod test_serialization {
);

let deserialized: ProviderInfo = serde_json::from_value(json).unwrap();
assert_eq!(deserialized.id, "main");
assert_eq!(deserialized.provider_id.to_string(), "main");
assert_eq!(deserialized.supported.len(), 2);
assert!(deserialized.required);
assert!(deserialized.current.is_some());
Expand All @@ -6451,14 +6480,14 @@ mod test_serialization {
assert_eq!(
json,
json!({
"id": "secondary",
"providerId": "secondary",
"supported": ["openai"],
"required": false
})
);

let deserialized: ProviderInfo = serde_json::from_value(json).unwrap();
assert_eq!(deserialized.id, "secondary");
assert_eq!(deserialized.provider_id.to_string(), "secondary");
assert!(!deserialized.required);
assert!(deserialized.current.is_none());
}
Expand All @@ -6468,7 +6497,7 @@ mod test_serialization {
fn test_provider_info_missing_current_defaults_to_none() {
// current is optional; omitting it should decode as None
let json = json!({
"id": "main",
"providerId": "main",
"supported": ["anthropic"],
"required": true
});
Expand All @@ -6483,7 +6512,7 @@ mod test_serialization {
// both must deserialize into None so the disabled state is preserved
// regardless of which form the peer chose to send.
let json = json!({
"id": "main",
"providerId": "main",
"supported": ["anthropic"],
"required": true,
"current": null
Expand All @@ -6507,7 +6536,7 @@ mod test_serialization {

let json = serde_json::to_value(&response).unwrap();
assert_eq!(json["providers"].as_array().unwrap().len(), 1);
assert_eq!(json["providers"][0]["id"], "main");
assert_eq!(json["providers"][0]["providerId"], "main");

let deserialized: ListProvidersResponse = serde_json::from_value(json).unwrap();
assert_eq!(deserialized.providers.len(), 1);
Expand All @@ -6529,7 +6558,7 @@ mod test_serialization {
assert_eq!(
json,
json!({
"id": "main",
"providerId": "main",
"apiType": "openai",
"baseUrl": "https://api.openai.com/v1",
"headers": {
Expand All @@ -6539,7 +6568,7 @@ mod test_serialization {
);

let deserialized: SetProviderRequest = serde_json::from_value(json).unwrap();
assert_eq!(deserialized.id, "main");
assert_eq!(deserialized.provider_id.to_string(), "main");
assert_eq!(deserialized.api_type, LlmProtocol::OpenAi);
assert_eq!(deserialized.base_url, "https://api.openai.com/v1");
assert_eq!(deserialized.headers.len(), 1);
Expand All @@ -6566,10 +6595,10 @@ mod test_serialization {
let request = DisableProviderRequest::new("secondary");

let json = serde_json::to_value(&request).unwrap();
assert_eq!(json, json!({ "id": "secondary" }));
assert_eq!(json, json!({ "providerId": "secondary" }));

let deserialized: DisableProviderRequest = serde_json::from_value(json).unwrap();
assert_eq!(deserialized.id, "secondary");
assert_eq!(deserialized.provider_id.to_string(), "secondary");
}

#[cfg(feature = "unstable_llm_providers")]
Expand Down
6 changes: 3 additions & 3 deletions agent-client-protocol-schema/src/v1/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2866,7 +2866,7 @@ mod tests {
"sessionUpdate": "plan_update",
"plan": {
"type": "items",
"id": "plan-1",
"planId": "plan-1",
"entries": [
{
"content": "Step 1",
Expand All @@ -2882,7 +2882,7 @@ mod tests {
serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
json!({
"sessionUpdate": "plan_removed",
"id": "plan-1"
"planId": "plan-1"
})
);

Expand Down Expand Up @@ -2934,7 +2934,7 @@ mod tests {

assert_eq!(
serde_json::to_value(ConnectMcpRequest::new("server-1")).unwrap(),
json!({ "acpId": "server-1" })
json!({ "serverId": "server-1" })
);
assert_eq!(
serde_json::to_value(ConnectMcpResponse::new("conn-1")).unwrap(),
Expand Down
6 changes: 3 additions & 3 deletions agent-client-protocol-schema/src/v1/mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl McpConnectionId {
#[non_exhaustive]
pub struct ConnectMcpRequest {
/// The ACP MCP server ID that was provided by the component declaring the MCP server.
pub acp_id: McpServerAcpId,
pub server_id: McpServerAcpId,
/// 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 @@ -60,9 +60,9 @@ pub struct ConnectMcpRequest {
impl ConnectMcpRequest {
/// Builds [`ConnectMcpRequest`] with the required request fields set; optional fields start unset or empty.
#[must_use]
pub fn new(acp_id: impl Into<McpServerAcpId>) -> Self {
pub fn new(server_id: impl Into<McpServerAcpId>) -> Self {
Self {
acp_id: acp_id.into(),
server_id: server_id.into(),
meta: None,
}
}
Expand Down
Loading