Skip to content

Commit 9b97c2e

Browse files
edburnsCopilot
andauthored
Add Rust low-level tool-definition E2E test [5/6] (#1727)
* 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> * fix: revert session_lifecycle.rs Say hi -> Say world to match snapshot The snapshot expects 'Say world' but the branch had changed it to 'Say hi', causing 'No cached response found' failures across all three OS variants. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent c795282 commit 9b97c2e

1 file changed

Lines changed: 114 additions & 2 deletions

File tree

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)