Skip to content
Open
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
5 changes: 2 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ agent-client-protocol-trace-viewer = { path = "src/agent-client-protocol-trace-v
yopo = { package = "agent-client-protocol-yopo", path = "src/yopo" }

# Protocol
agent-client-protocol-schema = { version = "=1.1.0", features = ["tracing"] }
agent-client-protocol-schema = { version = "=1.2.0", features = ["tracing"] }

# Core async runtime
tokio = { version = "1.52", default-features = false }
Expand Down Expand Up @@ -113,3 +113,7 @@ struct_field_names = "allow"
too_many_lines = "allow"
type_complexity = "allow"
wildcard_imports = "allow"

[patch.crates-io]
# Temporary: consume session/setTitle schema types before they are released.
agent-client-protocol-schema = { git = "https://github.com/daniel-agentee/agent-client-protocol", rev = "064ee1ffe194cdb14e1d45d66875ce74a14933c3" }
35 changes: 24 additions & 11 deletions src/agent-client-protocol-test/src/testy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use agent_client_protocol::schema::v1::{
CompleteElicitationNotification, CreateElicitationRequest, ElicitationAction,
ElicitationCapabilities, ElicitationFormMode, ElicitationRequestScope, ElicitationSchema,
ElicitationSessionScope, ElicitationUrlMode, ErrorCode, MultiSelectPropertySchema, RequestId,
StringPropertySchema, UrlElicitationRequiredData, UrlElicitationRequiredItem,
StringPropertySchema,
};
use agent_client_protocol::{
Agent, Client, ConnectTo, ConnectionTo, JsonRpcRequest, Responder, SentRequest,
Expand Down Expand Up @@ -637,7 +637,7 @@ impl Testy {
Err(error) => {
self.finish_prompt(&session_id, StopReason::EndTurn);
#[cfg(feature = "unstable")]
if error.code == ErrorCode::UrlElicitationRequired {
if is_url_elicitation_required_error(&error) {
return responder.respond_with_error(error);
}
return Err(error);
Expand Down Expand Up @@ -1765,15 +1765,28 @@ fn elicitation_cancelled(agent: &Testy, session_id: &SessionId, report: &mut Vec

#[cfg(feature = "unstable")]
fn url_elicitation_required_error() -> agent_client_protocol::Error {
let data = UrlElicitationRequiredData::new(vec![UrlElicitationRequiredItem::new(
"testy-url-required",
"https://example.com/testy/required",
"Complete the Testy URL elicitation before continuing",
)]);
match serde_json::to_value(data) {
Ok(data) => agent_client_protocol::Error::url_elicitation_required().data(data),
Err(error) => agent_client_protocol::Error::into_internal_error(error),
}
agent_client_protocol::Error::invalid_params().data(url_elicitation_required_data())
}

#[cfg(feature = "unstable")]
fn is_url_elicitation_required_error(error: &agent_client_protocol::Error) -> bool {
error.code == ErrorCode::InvalidParams
&& error
.data
.as_ref()
.is_some_and(|data| data.get("elicitations").is_some())
}

#[cfg(feature = "unstable")]
fn url_elicitation_required_data() -> serde_json::Value {
serde_json::json!({
"elicitations": [{
"mode": "url",
"elicitationId": "testy-url-required",
"url": "https://example.com/testy/required",
"message": "Complete the Testy URL elicitation before continuing"
}]
})
}

fn send_session_update(
Expand Down
24 changes: 12 additions & 12 deletions src/agent-client-protocol-test/tests/testy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use agent_client_protocol::schema::v1::{
CompleteElicitationNotification, CreateElicitationRequest, CreateElicitationResponse,
ElicitationAcceptAction, ElicitationAction, ElicitationCapabilities, ElicitationContentValue,
ElicitationFormCapabilities, ElicitationMode, ElicitationScope, ElicitationUrlCapabilities,
ErrorCode, UrlElicitationRequiredData,
ErrorCode,
};
use agent_client_protocol::{
Client, Responder,
Expand Down Expand Up @@ -1958,18 +1958,18 @@ async fn testy_callbacks_with_unstable_feature_returns_url_required_when_url_eli
.block_task()
.await
.expect_err("url-required elicitation scenario should fail the prompt request");
assert_eq!(error.code, ErrorCode::UrlElicitationRequired);

let data: UrlElicitationRequiredData =
serde_json::from_value(error.data.expect("url-required error should include data"))
.expect("url-required error data should deserialize");
assert_eq!(data.elicitations.len(), 1);
let elicitation = &data.elicitations[0];
assert_eq!(elicitation.elicitation_id.to_string(), "testy-url-required");
assert_eq!(elicitation.url, "https://example.com/testy/required");
assert_eq!(error.code, ErrorCode::InvalidParams);

assert_eq!(
elicitation.message,
"Complete the Testy URL elicitation before continuing"
error.data.expect("url-required error should include data"),
serde_json::json!({
"elicitations": [{
"mode": "url",
"elicitationId": "testy-url-required",
"url": "https://example.com/testy/required",
"message": "Complete the Testy URL elicitation before continuing"
}]
})
);
Ok(())
})
Expand Down
16 changes: 8 additions & 8 deletions src/agent-client-protocol/src/capabilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ mod tests {

#[test]
fn test_add_capability_to_request() {
let request = InitializeRequest::new(ProtocolVersion::LATEST);
let request = InitializeRequest::new(ProtocolVersion::V1);

let request = request.add_meta_capability(TestCapability);

Expand All @@ -165,8 +165,8 @@ mod tests {
);
let client_capabilities = ClientCapabilities::new().meta(meta);

let request = InitializeRequest::new(ProtocolVersion::LATEST)
.client_capabilities(client_capabilities);
let request =
InitializeRequest::new(ProtocolVersion::V1).client_capabilities(client_capabilities);

let request = request.remove_meta_capability(TestCapability);

Expand All @@ -175,7 +175,7 @@ mod tests {

#[test]
fn test_add_capability_to_response() {
let response = InitializeResponse::new(ProtocolVersion::LATEST);
let response = InitializeResponse::new(ProtocolVersion::V1);

let response = response.add_meta_capability(TestCapability);

Expand All @@ -198,8 +198,8 @@ mod tests {
);
let client_capabilities = ClientCapabilities::new().meta(meta);

let request = InitializeRequest::new(ProtocolVersion::LATEST)
.client_capabilities(client_capabilities);
let request =
InitializeRequest::new(ProtocolVersion::V1).client_capabilities(client_capabilities);

assert!(!request.has_meta_capability(TestCapability));
}
Expand All @@ -216,8 +216,8 @@ mod tests {
);
let client_capabilities = ClientCapabilities::new().meta(meta);

let request = InitializeRequest::new(ProtocolVersion::LATEST)
.client_capabilities(client_capabilities);
let request =
InitializeRequest::new(ProtocolVersion::V1).client_capabilities(client_capabilities);

assert!(!request.has_meta_capability(TestCapability));
}
Expand Down
49 changes: 42 additions & 7 deletions src/agent-client-protocol/src/jsonrpc/protocol_compat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,11 @@ mod imp {
}

fn public_to_v2_message(message: UntypedMessage) -> Result<UntypedMessage, crate::Error> {
let UntypedMessage { method, params } = message;
let UntypedMessage { method, mut params } = message;

if method == "initialize" {
ensure_legacy_initialize_info(&mut params, "clientInfo");
}

if let Some(message) = try_convert_message_to_v2::<ClientRequest>(&method, &params)? {
return Ok(message);
Expand Down Expand Up @@ -573,8 +577,12 @@ mod imp {

fn public_to_v2_response(
method: &str,
value: serde_json::Value,
mut value: serde_json::Value,
) -> Result<serde_json::Value, crate::Error> {
if method == "initialize" {
ensure_legacy_initialize_info(&mut value, "agentInfo");
}

if let Some(value) = try_convert_response_to_v2::<AgentResponse>(method, &value)? {
return Ok(value);
}
Expand All @@ -585,6 +593,27 @@ mod imp {
Ok(value)
}

fn ensure_legacy_initialize_info(value: &mut serde_json::Value, field: &str) {
let serde_json::Value::Object(object) = value else {
return;
};

let needs_default = match object.get(field) {
Some(value) => value.is_null(),
None => true,
};

if needs_default {
object.insert(
field.into(),
serde_json::json!({
"name": "legacy-acp-peer",
"version": "unknown",
}),
);
}
}

fn v2_to_public_response(
method: &str,
value: serde_json::Value,
Expand Down Expand Up @@ -728,6 +757,10 @@ mod imp {
.negotiated
}

fn v2_implementation() -> v2::Implementation {
v2::Implementation::new("rust-sdk-test", "0.0.0")
}

#[test]
fn initialize_request_sets_active_wire_version_before_response() -> Result<(), crate::Error>
{
Expand All @@ -736,7 +769,7 @@ mod imp {

compat.incoming_message(UntypedMessage::new(
"initialize",
v2::InitializeRequest::new(ProtocolVersion::V2),
v2::InitializeRequest::new(ProtocolVersion::V2, v2_implementation()),
)?)?;

assert_eq!(negotiated(&compat), ProtocolVersionKind::V1);
Expand All @@ -746,6 +779,7 @@ mod imp {
"initialize",
Ok(serde_json::to_value(v2::InitializeResponse::new(
ProtocolVersion::V2,
v2_implementation(),
))?),
)?;

Expand All @@ -762,7 +796,7 @@ mod imp {

compat.outgoing_message(UntypedMessage::new(
"initialize",
v2::InitializeRequest::new(ProtocolVersion::V1),
v2::InitializeRequest::new(ProtocolVersion::V1, v2_implementation()),
)?)?;

assert_eq!(negotiated(&compat), ProtocolVersionKind::V1);
Expand All @@ -772,6 +806,7 @@ mod imp {
"initialize",
Ok(serde_json::to_value(v2::InitializeResponse::new(
ProtocolVersion::V2,
v2_implementation(),
))?),
)?;

Expand All @@ -788,7 +823,7 @@ mod imp {

compat.outgoing_message(UntypedMessage::new(
"initialize",
v2::InitializeRequest::new(ProtocolVersion::V1),
v2::InitializeRequest::new(ProtocolVersion::V1, v2_implementation()),
)?)?;

assert_eq!(negotiated(&compat), ProtocolVersionKind::V1);
Expand All @@ -814,7 +849,7 @@ mod imp {
let compat = ProtocolCompat::new(ProtocolMode::v2_client());
compat.outgoing_message(UntypedMessage::new(
"initialize",
v2::InitializeRequest::new(ProtocolVersion::V1),
v2::InitializeRequest::new(ProtocolVersion::V1, v2_implementation()),
)?)?;

let error = compat
Expand All @@ -838,7 +873,7 @@ mod imp {
let compat = ProtocolCompat::new(ProtocolMode::v2_agent());
let messages = compat.outgoing_notification(UntypedMessage::new(
"session/update",
v2::SessionNotification::new(
v2::UpdateSessionNotification::new(
"sess",
v2::SessionUpdate::AgentMessage(v2::AgentMessage::new("msg_agent").content(
vec![
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::schema::v1::{
LogoutRequest, LogoutResponse, NewSessionRequest, NewSessionResponse, PromptRequest,
PromptResponse, ResumeSessionRequest, ResumeSessionResponse, SetSessionConfigOptionRequest,
SetSessionConfigOptionResponse, SetSessionModeRequest, SetSessionModeResponse,
SetSessionTitleRequest, SetSessionTitleResponse,
};
#[cfg(feature = "unstable_session_fork")]
use crate::schema::v1::{ForkSessionRequest, ForkSessionResponse};
Expand All @@ -31,6 +32,11 @@ impl_jsonrpc_request!(
SetSessionConfigOptionResponse,
"session/set_config_option"
);
impl_jsonrpc_request!(
SetSessionTitleRequest,
SetSessionTitleResponse,
"session/setTitle"
);

#[cfg(feature = "unstable_session_fork")]
impl_jsonrpc_request!(ForkSessionRequest, ForkSessionResponse, "session/fork");
Expand Down
1 change: 1 addition & 0 deletions src/agent-client-protocol/src/schema/enum_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ impl_jsonrpc_request_enum!(ClientRequest {
CloseSessionRequest => "session/close",
SetSessionModeRequest => "session/set_mode",
SetSessionConfigOptionRequest => "session/set_config_option",
SetSessionTitleRequest => "session/setTitle",
PromptRequest => "session/prompt",
#[cfg(feature = "unstable_mcp_over_acp")]
MessageMcpRequest => "mcp/message",
Expand Down
Loading