use github_copilot_sdk::CustomAgentConfig; use github_copilot_sdk::rpc::{AgentInfo, AgentSelectRequest}; use github_copilot_sdk::session_events::SessionEventType; use serde_json::json; use super::support::wait_for_event; #[tokio::test] async fn should_list_available_custom_agents() { super::support::with_shared_e2e_context( &E2E, "rpc_agents", "should_list_available_custom_agents", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); let client = ctx.start_client().await; let session = client .create_session( ctx.approve_all_session_config() .with_custom_agents(create_custom_agents()), ) .await .expect("create session"); let result = session.rpc().agent().list().await.expect("agent list"); assert_agent(&result.agents, "test-agent", "Test Agent", "A test agent"); assert_agent( &result.agents, "another-agent", "Another Agent", "Another test agent", ); session.disconnect().await.expect("disconnect session"); client.stop().await.expect("stop client"); }) }, ) .await; } #[tokio::test] async fn should_return_null_when_no_agent_is_selected() { super::support::with_shared_e2e_context( &E2E, "rpc_agents", "should_return_null_when_no_agent_is_selected", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); let client = ctx.start_client().await; let session = client .create_session( ctx.approve_all_session_config() .with_custom_agents([create_custom_agents().remove(0)]), ) .await .expect("create session"); let value = client .call( "session.agent.getCurrent", Some(json!({ "sessionId": session.id() })), ) .await .expect("get current agent"); assert!(value.get("agent").is_some_and(serde_json::Value::is_null)); session.disconnect().await.expect("disconnect session"); client.stop().await.expect("stop client"); }) }, ) .await; } #[tokio::test] async fn should_select_and_get_current_agent() { super::support::with_shared_e2e_context( &E2E, "rpc_agents", "should_select_and_get_current_agent", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); let client = ctx.start_client().await; let session = client .create_session( ctx.approve_all_session_config() .with_custom_agents([create_custom_agents().remove(0)]), ) .await .expect("create session"); let selected = session .rpc() .agent() .select(AgentSelectRequest { name: "test-agent".to_string(), }) .await .expect("select agent"); assert_eq!(selected.agent.name, "test-agent"); assert_eq!(selected.agent.display_name, "Test Agent"); let current = session .rpc() .agent() .get_current() .await .expect("get selected agent"); assert_eq!(current.agent.name, "test-agent"); session.disconnect().await.expect("disconnect session"); client.stop().await.expect("stop client"); }) }, ) .await; } #[tokio::test] async fn should_emit_subagent_selected_and_deselected_events() { super::support::with_shared_e2e_context( &E2E, "rpc_agents", "should_emit_subagent_selected_and_deselected_events", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); let client = ctx.start_client().await; let session = client .create_session( ctx.approve_all_session_config() .with_custom_agents([create_custom_agents().remove(0)]), ) .await .expect("create session"); let selected_event = wait_for_event(session.subscribe(), "subagent selected", |event| { event.parsed_type() == SessionEventType::SubagentSelected }); session .rpc() .agent() .select(AgentSelectRequest { name: "test-agent".to_string(), }) .await .expect("select agent"); let selected = selected_event.await; assert_eq!( selected .data .get("agentName") .and_then(serde_json::Value::as_str), Some("test-agent") ); assert_eq!( selected .data .get("agentDisplayName") .and_then(serde_json::Value::as_str), Some("Test Agent") ); let deselected_event = wait_for_event(session.subscribe(), "subagent deselected", |event| { event.parsed_type() == SessionEventType::SubagentDeselected }); session .rpc() .agent() .deselect() .await .expect("deselect agent"); deselected_event.await; let value = client .call( "session.agent.getCurrent", Some(json!({ "sessionId": session.id() })), ) .await .expect("get current agent after deselect"); assert!(value.get("agent").is_some_and(serde_json::Value::is_null)); session.disconnect().await.expect("disconnect session"); client.stop().await.expect("stop client"); }) }, ) .await; } #[tokio::test] async fn should_deselect_current_agent() { super::support::with_shared_e2e_context( &E2E, "rpc_agents", "should_deselect_current_agent", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); let client = ctx.start_client().await; let session = client .create_session( ctx.approve_all_session_config() .with_custom_agents([create_custom_agents().remove(0)]), ) .await .expect("create session"); session .rpc() .agent() .select(AgentSelectRequest { name: "test-agent".to_string(), }) .await .expect("select agent"); session .rpc() .agent() .deselect() .await .expect("deselect agent"); let value = client .call( "session.agent.getCurrent", Some(json!({ "sessionId": session.id() })), ) .await .expect("get current agent"); assert!(value.get("agent").is_some_and(serde_json::Value::is_null)); session.disconnect().await.expect("disconnect session"); client.stop().await.expect("stop client"); }) }, ) .await; } #[tokio::test] async fn should_return_empty_list_when_no_custom_agents_configured() { super::support::with_shared_e2e_context( &E2E, "rpc_agents", "should_return_empty_list_when_no_custom_agents_configured", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); let client = ctx.start_client().await; let session = client .create_session(ctx.approve_all_session_config()) .await .expect("create session"); let result = session.rpc().agent().list().await.expect("agent list"); assert!(result.agents.is_empty()); session.disconnect().await.expect("disconnect session"); client.stop().await.expect("stop client"); }) }, ) .await; } #[tokio::test] async fn should_call_agent_reload() { super::support::with_shared_e2e_context( &E2E, "rpc_agents", "should_call_agent_reload", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); let reload_agent = CustomAgentConfig::new( "reload-test-agent-rust", "You are a reload test agent.", ) .with_display_name("Reload Test Agent") .with_description("Used by the agent reload RPC test."); let client = ctx.start_client().await; let session = client .create_session( ctx.approve_all_session_config() .with_custom_agents([reload_agent.clone()]), ) .await .expect("create session"); assert_agent( &session .rpc() .agent() .list() .await .expect("list before") .agents, "reload-test-agent-rust", "Reload Test Agent", "Used by the agent reload RPC test.", ); let reloaded = session.rpc().agent().reload().await.expect("reload agents"); let current = session.rpc().agent().list().await.expect("list after"); assert_eq!( agent_names(&reloaded.agents), agent_names(¤t.agents), "reload result should match current list" ); session.disconnect().await.expect("disconnect session"); client.stop().await.expect("stop client"); }) }, ) .await; } fn create_custom_agents() -> Vec { vec![ CustomAgentConfig::new("test-agent", "You are a test agent.") .with_display_name("Test Agent") .with_description("A test agent"), CustomAgentConfig::new("another-agent", "You are another agent.") .with_display_name("Another Agent") .with_description("Another test agent"), ] } fn assert_agent(agents: &[AgentInfo], name: &str, display_name: &str, description: &str) { let agent = agents .iter() .find(|agent| agent.name == name) .unwrap_or_else(|| panic!("missing agent {name}; actual agents: {agents:?}")); assert_eq!(agent.display_name, display_name); assert_eq!(agent.description, description); } fn agent_names(agents: &[AgentInfo]) -> Vec<&str> { let mut names: Vec<_> = agents.iter().map(|agent| agent.name.as_str()).collect(); names.sort_unstable(); names } static E2E: super::support::SharedE2eGroup = super::support::SharedE2eGroup::standard("rpc_agents", 7);