Skip to content

Commit aebe7c2

Browse files
edburnsCopilot
andcommitted
Add Rust low-level tool-definition E2E test
Related to issue #1682 but does not fix #1682. Align low_level_tool_definition coverage with PR #1721 snapshot behavior by only defining tools exercised by the shared snapshot. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 0763c09 commit aebe7c2

2 files changed

Lines changed: 115 additions & 3 deletions

File tree

rust/tests/e2e/session_lifecycle.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ async fn should_list_created_sessions_after_sending_a_message() {
2424
.expect("create second session");
2525

2626
session1.send_and_wait("Say hello").await.expect("send one");
27-
session2.send_and_wait("Say world").await.expect("send two");
27+
session2.send_and_wait("Say hi").await.expect("send two");
2828

2929
wait_for_condition("both sessions to appear in list", || {
3030
let client = client.clone();

rust/tests/e2e/tools.rs

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ use github_copilot_sdk::handler::{ApproveAllHandler, PermissionHandler, Permissi
44
use github_copilot_sdk::tool::ToolHandler;
55
use github_copilot_sdk::{
66
Error, PermissionRequestData, RequestId, SessionConfig, SessionId, Tool, ToolInvocation,
7-
ToolResult,
7+
ToolResult, ToolSet,
88
};
99
use serde_json::json;
10-
use tokio::sync::mpsc;
10+
use tokio::sync::{Mutex, mpsc};
1111

1212
use super::support::{assistant_message_content, recv_with_timeout, with_e2e_context};
1313

@@ -73,6 +73,55 @@ async fn invokes_custom_tool() {
7373
.await;
7474
}
7575

76+
#[tokio::test]
77+
async fn low_level_tool_definition() {
78+
with_e2e_context("tools", "low_level_tool_definition", |ctx| {
79+
Box::pin(async move {
80+
ctx.set_default_copilot_user();
81+
let client = ctx.start_client().await;
82+
let __perm = Arc::new(ApproveAllHandler);
83+
let current_phase = Arc::new(Mutex::new(String::new()));
84+
let tools = vec![
85+
set_current_phase_tool(current_phase.clone()),
86+
search_items_tool(),
87+
];
88+
let available_tools = ToolSet::new()
89+
.add_custom("*")
90+
.expect("add custom wildcard")
91+
.add_builtin("web_fetch")
92+
.expect("add web_fetch")
93+
.into_vec();
94+
let session = client
95+
.create_session(
96+
SessionConfig::default()
97+
.with_github_token(super::support::DEFAULT_TEST_TOKEN)
98+
.with_permission_handler(__perm)
99+
.with_tools(tools)
100+
.with_available_tools(available_tools),
101+
)
102+
.await
103+
.expect("create session");
104+
105+
let answer = session
106+
.send_and_wait(
107+
"First, set the current phase to 'analyzing'. Then search for items with keyword 'copilot'. Report the phase and search results.",
108+
)
109+
.await
110+
.expect("send")
111+
.expect("assistant message");
112+
let content = assistant_message_content(&answer);
113+
assert!(!content.is_empty());
114+
assert!(content.to_lowercase().contains("analyzing"));
115+
assert!(content.contains("item_alpha") || content.contains("item_beta"));
116+
assert_eq!(current_phase.lock().await.clone(), "analyzing");
117+
118+
session.disconnect().await.expect("disconnect session");
119+
client.stop().await.expect("stop client");
120+
})
121+
})
122+
.await;
123+
}
124+
76125
#[tokio::test]
77126
async fn handles_tool_calling_errors() {
78127
with_e2e_context("tools", "handles_tool_calling_errors", |ctx| {
@@ -502,6 +551,69 @@ impl ToolHandler for ErrorTool {
502551

503552
struct CustomGrepTool;
504553

554+
struct SetCurrentPhaseTool {
555+
current_phase: Arc<Mutex<String>>,
556+
}
557+
558+
fn set_current_phase_tool(current_phase: Arc<Mutex<String>>) -> Tool {
559+
Tool::new("set_current_phase")
560+
.with_description("Sets the current phase of the agent")
561+
.with_parameters(json!({
562+
"type": "object",
563+
"properties": {
564+
"phase": {
565+
"type": "string",
566+
"description": "Current phase",
567+
"pattern": "^(searching|analyzing|done)$"
568+
}
569+
},
570+
"required": ["phase"]
571+
}))
572+
.with_handler(Arc::new(SetCurrentPhaseTool { current_phase }))
573+
}
574+
575+
#[async_trait::async_trait]
576+
impl ToolHandler for SetCurrentPhaseTool {
577+
async fn call(&self, invocation: ToolInvocation) -> Result<ToolResult, Error> {
578+
let phase = invocation
579+
.arguments
580+
.get("phase")
581+
.and_then(serde_json::Value::as_str)
582+
.unwrap_or_default()
583+
.to_string();
584+
*self.current_phase.lock().await = phase.clone();
585+
Ok(ToolResult::Text(format!("Phase set to {phase}")))
586+
}
587+
}
588+
589+
struct SearchItemsTool;
590+
591+
fn search_items_tool() -> Tool {
592+
Tool::new("search_items")
593+
.with_description("Search for items by keyword")
594+
.with_parameters(json!({
595+
"type": "object",
596+
"properties": {
597+
"keyword": { "type": "string" }
598+
},
599+
"required": ["keyword"]
600+
}))
601+
.with_handler(Arc::new(SearchItemsTool))
602+
}
603+
604+
#[async_trait::async_trait]
605+
impl ToolHandler for SearchItemsTool {
606+
async fn call(&self, invocation: ToolInvocation) -> Result<ToolResult, Error> {
607+
let keyword = invocation
608+
.arguments
609+
.get("keyword")
610+
.and_then(serde_json::Value::as_str)
611+
.unwrap_or_default();
612+
assert_eq!(keyword, "copilot");
613+
Ok(ToolResult::Text("Found: item_alpha, item_beta".to_string()))
614+
}
615+
}
616+
505617
fn custom_grep_tool() -> Tool {
506618
Tool::new("grep")
507619
.with_description("A custom grep implementation that overrides the built-in")

0 commit comments

Comments
 (0)