/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
package com.github.copilot.sdk;
import static org.junit.jupiter.api.Assertions.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import com.github.copilot.sdk.events.AssistantMessageEvent;
import com.github.copilot.sdk.json.MessageOptions;
import com.github.copilot.sdk.json.PermissionHandler;
import com.github.copilot.sdk.json.PermissionRequest;
import com.github.copilot.sdk.json.PermissionRequestResult;
import com.github.copilot.sdk.json.SessionConfig;
import com.github.copilot.sdk.json.ToolDefinition;
/**
* Tests for custom tools functionality.
*
*
* These tests use the shared CapiProxy infrastructure for deterministic API
* response replay. Snapshots are stored in test/snapshots/tools/.
*
*/
public class ToolsTest {
private static E2ETestContext ctx;
@BeforeAll
static void setup() throws Exception {
ctx = E2ETestContext.create();
}
@AfterAll
static void teardown() throws Exception {
if (ctx != null) {
ctx.close();
}
}
/**
* Verifies that built-in tools are invoked correctly.
*
* @see Snapshot: tools/invokes_built_in_tools
*/
@Test
void testInvokesBuiltInTools(TestInfo testInfo) throws Exception {
ctx.configureForTest("tools", "invokes_built_in_tools");
// Write a test file
Path readmeFile = ctx.getWorkDir().resolve("README.md");
Files.writeString(readmeFile, "# ELIZA, the only chatbot you'll ever need");
try (CopilotClient client = ctx.createClient()) {
CopilotSession session = client
.createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get();
AssistantMessageEvent response = session
.sendAndWait(
new MessageOptions().setPrompt("What's the first line of README.md in this directory?"))
.get(60, TimeUnit.SECONDS);
assertNotNull(response);
assertTrue(response.getData().content().contains("ELIZA"),
"Response should contain ELIZA: " + response.getData().content());
session.close();
}
}
/**
* Verifies that custom tools are invoked correctly.
*
* @see Snapshot: tools/invokes_custom_tool
*/
@Test
void testInvokesCustomTool(TestInfo testInfo) throws Exception {
ctx.configureForTest("tools", "invokes_custom_tool");
// Define a simple encrypt_string tool
var parameters = new HashMap();
var properties = new HashMap();
var inputProp = new HashMap();
inputProp.put("type", "string");
inputProp.put("description", "String to encrypt");
properties.put("input", inputProp);
parameters.put("type", "object");
parameters.put("properties", properties);
parameters.put("required", List.of("input"));
ToolDefinition encryptTool = ToolDefinition.create("encrypt_string", "Encrypts a string", parameters,
(invocation) -> {
Map args = invocation.getArguments();
String input = (String) args.get("input");
return CompletableFuture.completedFuture(input.toUpperCase());
});
try (CopilotClient client = ctx.createClient()) {
CopilotSession session = client.createSession(new SessionConfig().setTools(List.of(encryptTool))
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get();
AssistantMessageEvent response = session
.sendAndWait(new MessageOptions().setPrompt("Use encrypt_string to encrypt this string: Hello"))
.get(60, TimeUnit.SECONDS);
assertNotNull(response);
assertTrue(response.getData().content().contains("HELLO"),
"Response should contain HELLO: " + response.getData().content());
session.close();
}
}
/**
* Verifies that tool calling errors are handled gracefully.
*
* @see Snapshot: tools/handles_tool_calling_errors
*/
@Test
void testHandlesToolCallingErrors(TestInfo testInfo) throws Exception {
ctx.configureForTest("tools", "handles_tool_calling_errors");
// Define a tool that throws an error
var parameters = new HashMap();
parameters.put("type", "object");
parameters.put("properties", new HashMap<>());
ToolDefinition errorTool = ToolDefinition.create("get_user_location", "Gets the user's location", parameters,
(invocation) -> {
CompletableFuture