/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
package com.github.copilot.sdk;
import static org.junit.jupiter.api.Assertions.*;
import java.util.ArrayList;
import java.util.List;
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.Disabled;
import org.junit.jupiter.api.Test;
import com.github.copilot.sdk.json.MessageOptions;
import com.github.copilot.sdk.json.SessionConfig;
import com.github.copilot.sdk.json.UserInputRequest;
import com.github.copilot.sdk.json.UserInputResponse;
/**
* Tests for user input handler (ask_user) functionality.
*
*
* These tests use the shared CapiProxy infrastructure for deterministic API
* response replay. Snapshots are stored in test/snapshots/ask-user/.
*
*
*
* Note: These tests require the latest test harness with ask_user
* support.
*
*/
@Disabled("Requires test harness update with ask_user support - see upstream PR #269")
public class AskUserTest {
private static E2ETestContext ctx;
@BeforeAll
static void setup() throws Exception {
ctx = E2ETestContext.create();
}
@AfterAll
static void teardown() throws Exception {
if (ctx != null) {
ctx.close();
}
}
@Test
void testUserInputHandlerInvokedWhenModelUsesAskUserTool() throws Exception {
ctx.configureForTest("ask-user", "should_invoke_user_input_handler_when_model_uses_ask_user_tool");
List userInputRequests = new ArrayList<>();
final String[] sessionIdHolder = new String[1];
SessionConfig config = new SessionConfig().setOnUserInputRequest((request, invocation) -> {
userInputRequests.add(request);
assertEquals(sessionIdHolder[0], invocation.getSessionId());
// Return the first choice if available, otherwise a freeform answer
String answer = (request.getChoices() != null && !request.getChoices().isEmpty())
? request.getChoices().get(0)
: "freeform answer";
boolean wasFreeform = request.getChoices() == null || request.getChoices().isEmpty();
return CompletableFuture
.completedFuture(new UserInputResponse().setAnswer(answer).setWasFreeform(wasFreeform));
});
try (CopilotClient client = ctx.createClient()) {
CopilotSession session = client.createSession(config).get();
sessionIdHolder[0] = session.getSessionId();
session.sendAndWait(new MessageOptions().setPrompt(
"Ask me to choose between 'Option A' and 'Option B' using the ask_user tool. Wait for my response before continuing."))
.get(60, TimeUnit.SECONDS);
// Should have received at least one user input request
assertFalse(userInputRequests.isEmpty(), "Should have received user input requests");
// The request should have a question
assertTrue(userInputRequests.stream().anyMatch(r -> r.getQuestion() != null && !r.getQuestion().isEmpty()),
"User input request should have a question");
}
}
@Test
void testUserInputRequestWithChoices() throws Exception {
ctx.configureForTest("ask-user", "should_receive_choices_in_user_input_request");
List userInputRequests = new ArrayList<>();
SessionConfig config = new SessionConfig().setOnUserInputRequest((request, invocation) -> {
userInputRequests.add(request);
// Pick the first choice
String answer = (request.getChoices() != null && !request.getChoices().isEmpty())
? request.getChoices().get(0)
: "default";
return CompletableFuture.completedFuture(new UserInputResponse().setAnswer(answer).setWasFreeform(false));
});
try (CopilotClient client = ctx.createClient()) {
CopilotSession session = client.createSession(config).get();
session.sendAndWait(new MessageOptions().setPrompt(
"Use the ask_user tool to ask me to pick between exactly two options: 'Red' and 'Blue'. These should be provided as choices. Wait for my answer."))
.get(60, TimeUnit.SECONDS);
// Should have received a request
assertFalse(userInputRequests.isEmpty(), "Should have received user input requests");
// At least one request should have choices
assertTrue(userInputRequests.stream().anyMatch(r -> r.getChoices() != null && !r.getChoices().isEmpty()),
"At least one request should have choices");
}
}
@Test
void testFreeformUserInputResponse() throws Exception {
ctx.configureForTest("ask-user", "should_handle_freeform_user_input_response");
List userInputRequests = new ArrayList<>();
String freeformAnswer = "This is my custom freeform answer that was not in the choices";
SessionConfig config = new SessionConfig().setOnUserInputRequest((request, invocation) -> {
userInputRequests.add(request);
// Return a freeform answer (not from choices)
return CompletableFuture
.completedFuture(new UserInputResponse().setAnswer(freeformAnswer).setWasFreeform(true));
});
try (CopilotClient client = ctx.createClient()) {
CopilotSession session = client.createSession(config).get();
var response = session.sendAndWait(new MessageOptions().setPrompt(
"Ask me a question using ask_user and then include my answer in your response. The question should be 'What is your favorite color?'"))
.get(60, TimeUnit.SECONDS);
// Should have received a request
assertFalse(userInputRequests.isEmpty(), "Should have received user input requests");
// The model's response should be defined
assertNotNull(response, "Response should not be null");
}
}
}