Skip to content
Closed
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
23 changes: 21 additions & 2 deletions Cargo.lock

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

2 changes: 1 addition & 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.4.0", features = ["tracing"] }

# Core async runtime
tokio = { version = "1.52", default-features = false }
Expand Down
4 changes: 2 additions & 2 deletions md/testy.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,5 @@ available commands, mode/config/session info, and usage.

With default features, `elicitations`, `callbacks`, and `full` cover `elicitation/create` form mode,
URL mode, session scope, request scope, accept, decline, cancel, and `elicitation/complete`.
If the client advertises form elicitation but not URL elicitation, the URL part returns a
`UrlElicitationRequired` prompt error with deterministic error data.
If the client advertises form elicitation but not URL elicitation, the URL portion is skipped and
the scenario completes normally with a skipped-unsupported report.
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,9 @@ async fn test_single_component_gets_initialize_request() -> Result<(), agent_cli
// Single component (agent) should receive InitializeRequest - we use ElizaAgent
// which properly handles InitializeRequest
run_test_with_components(vec![], async |connection_to_editor| {
let init_response = recv(
connection_to_editor.send_request(InitializeRequest::new(ProtocolVersion::LATEST)),
)
.await;
let init_response =
recv(connection_to_editor.send_request(InitializeRequest::new(ProtocolVersion::V1)))
.await;

assert!(
init_response.is_ok(),
Expand All @@ -180,7 +179,7 @@ async fn test_two_components_proxy_gets_initialize_proxy()
vec![InitComponent::new(&component1)],
async |connection_to_editor| {
let init_response = recv(
connection_to_editor.send_request(InitializeRequest::new(ProtocolVersion::LATEST)),
connection_to_editor.send_request(InitializeRequest::new(ProtocolVersion::V1)),
)
.await;

Expand Down Expand Up @@ -221,7 +220,7 @@ async fn test_three_components_all_proxies_get_initialize_proxy()
],
async |connection_to_editor| {
let init_response = recv(
connection_to_editor.send_request(InitializeRequest::new(ProtocolVersion::LATEST)),
connection_to_editor.send_request(InitializeRequest::new(ProtocolVersion::V1)),
)
.await;

Expand Down Expand Up @@ -323,7 +322,7 @@ async fn test_conductor_rejects_initialize_proxy_forwarded_to_agent()
DynConnectTo::new(Testy::new()),
async |connection_to_editor| {
let init_response = recv(
connection_to_editor.send_request(InitializeRequest::new(ProtocolVersion::LATEST)),
connection_to_editor.send_request(InitializeRequest::new(ProtocolVersion::V1)),
)
.await;

Expand Down Expand Up @@ -365,7 +364,7 @@ async fn test_conductor_rejects_initialize_proxy_forwarded_to_proxy()
DynConnectTo::new(Testy::new()), // Agent
async |connection_to_editor| {
let init_response = recv(
connection_to_editor.send_request(InitializeRequest::new(ProtocolVersion::LATEST)),
connection_to_editor.send_request(InitializeRequest::new(ProtocolVersion::V1)),
)
.await;

Expand Down
7 changes: 3 additions & 4 deletions src/agent-client-protocol-conductor/tests/mcp-integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ async fn test_proxy_provides_mcp_tools_stdio() -> Result<(), agent_client_protoc
async |connection_to_editor| {
// Send initialization request
let init_response = recv(
connection_to_editor.send_request(InitializeRequest::new(ProtocolVersion::LATEST)),
connection_to_editor.send_request(InitializeRequest::new(ProtocolVersion::V1)),
)
.await;

Expand Down Expand Up @@ -123,7 +123,7 @@ async fn test_proxy_provides_mcp_tools_http() -> Result<(), agent_client_protoco
async |connection_to_editor| {
// Send initialization request
let init_response = recv(
connection_to_editor.send_request(InitializeRequest::new(ProtocolVersion::LATEST)),
connection_to_editor.send_request(InitializeRequest::new(ProtocolVersion::V1)),
)
.await;

Expand Down Expand Up @@ -214,8 +214,7 @@ async fn test_agent_handles_prompt() -> Result<(), agent_client_protocol::Error>
async |connection_to_editor| {
// Initialize
recv(
connection_to_editor
.send_request(InitializeRequest::new(ProtocolVersion::LATEST)),
connection_to_editor.send_request(InitializeRequest::new(ProtocolVersion::V1)),
)
.await?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,9 @@ async fn test_new_session_handler_invoked_with_mcp_server()

run_test(vec![proxy], agent, async |connection_to_editor| {
// Initialize first
let _init_response = recv(
connection_to_editor.send_request(InitializeRequest::new(ProtocolVersion::LATEST)),
)
.await?;
let _init_response =
recv(connection_to_editor.send_request(InitializeRequest::new(ProtocolVersion::V1)))
.await?;

// Create a new session - this should trigger the handler in the proxy
let session_response =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ async fn conductor_proxy_chain_preserves_prompt_meta() -> Result<(), agent_clien
run_with_conductor(
ProxiesAndAgent::new(agent).proxy(PassthroughProxy),
async |connection| {
recv(connection.send_request(InitializeRequest::new(ProtocolVersion::LATEST))).await?;
recv(connection.send_request(InitializeRequest::new(ProtocolVersion::V1))).await?;

let session = recv(connection.send_request(NewSessionRequest::new("/"))).await?;
recv(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ async fn test_scoped_mcp_server_through_session() -> Result<(), agent_client_pro
async |cx| {
// Initialize first
cx.send_request(agent_client_protocol::schema::v1::InitializeRequest::new(
agent_client_protocol::schema::ProtocolVersion::LATEST,
agent_client_protocol::schema::ProtocolVersion::V1,
))
.block_task()
.await?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ async fn test_trace_client_mcp_server() -> Result<(), agent_client_protocol::Err
),
async |cx| {
// Initialize
cx.send_request(InitializeRequest::new(ProtocolVersion::LATEST))
cx.send_request(InitializeRequest::new(ProtocolVersion::V1))
.block_task()
.await?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ async fn test_trace_mcp_tool_call() -> Result<(), agent_client_protocol::Error>
),
async |cx| {
// Initialize
recv(cx.send_request(InitializeRequest::new(ProtocolVersion::LATEST))).await?;
recv(cx.send_request(InitializeRequest::new(ProtocolVersion::V1))).await?;

// Create session
let session = recv(
Expand Down
2 changes: 1 addition & 1 deletion src/agent-client-protocol-test/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Expand the `testy` binary into a deterministic ACP test agent that can exercise stable v1 agent methods, notifications, session updates, and client callbacks.
- Add default `testy` coverage through the `unstable` cargo feature for elicitation form/URL requests, session/request scopes, response actions, completion notifications, URL-required prompt errors, and a direct `elicitations` prompt trigger.
- Add default `testy` coverage through the `unstable` cargo feature for elicitation form/URL requests, session/request scopes, response actions, completion notifications, unsupported URL-mode skips, and a direct `elicitations` prompt trigger.
25 changes: 5 additions & 20 deletions src/agent-client-protocol-test/src/testy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ use agent_client_protocol::schema::v1::{
use agent_client_protocol::schema::v1::{
CompleteElicitationNotification, CreateElicitationRequest, ElicitationAction,
ElicitationCapabilities, ElicitationFormMode, ElicitationRequestScope, ElicitationSchema,
ElicitationSessionScope, ElicitationUrlMode, ErrorCode, MultiSelectPropertySchema, RequestId,
StringPropertySchema, UrlElicitationRequiredData, UrlElicitationRequiredItem,
ElicitationSessionScope, ElicitationUrlMode, MultiSelectPropertySchema, RequestId,
StringPropertySchema,
};
use agent_client_protocol::{
Agent, Client, ConnectTo, ConnectionTo, JsonRpcRequest, Responder, SentRequest,
Expand Down Expand Up @@ -636,10 +636,6 @@ impl Testy {
Ok(response) => response,
Err(error) => {
self.finish_prompt(&session_id, StopReason::EndTurn);
#[cfg(feature = "unstable")]
if error.code == ErrorCode::UrlElicitationRequired {
return responder.respond_with_error(error);
}
return Err(error);
}
};
Expand Down Expand Up @@ -1188,7 +1184,9 @@ impl Testy {
}

if !self.client_supports_url_elicitation() {
return Err(url_elicitation_required_error());
report.push("elicitation/url: skipped unsupported".to_string());
report.push("elicitations: completed".to_string());
return Ok(());
}

let session_url_id = "testy-url-session";
Expand Down Expand Up @@ -1763,19 +1761,6 @@ 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),
}
}

fn send_session_update(
connection: &ConnectionTo<Client>,
session_id: &SessionId,
Expand Down
45 changes: 26 additions & 19 deletions src/agent-client-protocol-test/tests/testy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use agent_client_protocol::schema::v1::{
CompleteElicitationNotification, CreateElicitationRequest, CreateElicitationResponse,
ElicitationAcceptAction, ElicitationAction, ElicitationCapabilities, ElicitationContentValue,
ElicitationFormCapabilities, ElicitationMode, ElicitationScope, ElicitationUrlCapabilities,
ErrorCode, UrlElicitationRequiredData,
};
use agent_client_protocol::{
Client, Responder,
Expand Down Expand Up @@ -1840,12 +1839,29 @@ async fn testy_elicitations_prompt_exercises_all_elicitation_create_and_complete

#[cfg(feature = "unstable")]
#[tokio::test]
async fn testy_callbacks_with_unstable_feature_returns_url_required_when_url_elicitation_is_unsupported()
async fn testy_callbacks_with_unstable_feature_skips_url_elicitation_when_unsupported()
-> Result<(), agent_client_protocol::Error> {
let requests = Arc::new(Mutex::new(Vec::<&'static str>::new()));
let agent_messages = Arc::new(Mutex::new(Vec::<String>::new()));

Client
.builder()
.on_receive_notification(
{
let agent_messages = Arc::clone(&agent_messages);
async move |notification: SessionNotification, _cx| {
let SessionUpdate::AgentMessageChunk(chunk) = notification.update else {
return Ok(());
};
let ContentBlock::Text(text) = chunk.content else {
return Ok(());
};
agent_messages.lock().unwrap().push(text.text);
Ok(())
}
},
agent_client_protocol::on_receive_notification!(),
)
.on_receive_request(
async move |request: RequestPermissionRequest, responder, _cx| {
let option_id = request.options.first().map_or_else(
Expand Down Expand Up @@ -1925,7 +1941,7 @@ async fn testy_callbacks_with_unstable_feature_returns_url_required_when_url_eli
),
"form_session_decline" => ElicitationAction::Decline,
"form_request_cancel" => ElicitationAction::Cancel,
other => panic!("unexpected elicitation request before URL error: {other}"),
other => panic!("unexpected elicitation request: {other}"),
};
responder.respond(CreateElicitationResponse::new(action))
}
Expand All @@ -1944,7 +1960,7 @@ async fn testy_callbacks_with_unstable_feature_returns_url_required_when_url_eli
.block_task()
.await?;

let error = cx
let response = cx
.send_request(PromptRequest::new(
session.session_id,
vec![
Expand All @@ -1956,21 +1972,8 @@ 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!(
elicitation.message,
"Complete the Testy URL elicitation before continuing"
);
.await?;
assert_eq!(response.stop_reason, StopReason::EndTurn);
Ok(())
})
.await?;
Expand All @@ -1984,6 +1987,10 @@ async fn testy_callbacks_with_unstable_feature_returns_url_required_when_url_eli
]
);

let messages = agent_messages.lock().unwrap().join("\n");
assert!(messages.contains("elicitation/url: skipped unsupported"));
assert!(messages.contains("elicitations: completed"));

Ok(())
}

Expand Down
5 changes: 3 additions & 2 deletions src/agent-client-protocol/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ unstable = [
"unstable_session_fork",
]
unstable_auth_methods = ["agent-client-protocol-schema/unstable_auth_methods"]
unstable_boolean_config = ["agent-client-protocol-schema/unstable_boolean_config"]
unstable_cancel_request = ["agent-client-protocol-schema/unstable_cancel_request"]
# Kept as compatibility aliases for schema features that stabilized before schema 1.4.0.
unstable_boolean_config = []
unstable_cancel_request = []
unstable_elicitation = ["agent-client-protocol-schema/unstable_elicitation"]
unstable_end_turn_token_usage = ["agent-client-protocol-schema/unstable_end_turn_token_usage"]
unstable_mcp_over_acp = ["agent-client-protocol-schema/unstable_mcp_over_acp"]
Expand Down
Loading