/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
package com.github.copilot.sdk;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.github.copilot.sdk.events.AbstractSessionEvent;
import com.github.copilot.sdk.events.AbortEvent;
import com.github.copilot.sdk.events.AssistantMessageDeltaEvent;
import com.github.copilot.sdk.events.AssistantMessageEvent;
import com.github.copilot.sdk.events.SessionIdleEvent;
import com.github.copilot.sdk.events.SessionStartEvent;
import com.github.copilot.sdk.events.ToolExecutionStartEvent;
import com.github.copilot.sdk.events.UserMessageEvent;
import com.github.copilot.sdk.json.MessageOptions;
import com.github.copilot.sdk.json.SessionConfig;
import com.github.copilot.sdk.json.SystemMessageConfig;
import com.github.copilot.sdk.json.ToolDefinition;
/**
* Tests for CopilotSession.
*
*
* These tests use the shared CapiProxy infrastructure for deterministic API
* response replay. Snapshots are stored in test/snapshots/session/.
*
*/
public class CopilotSessionTest {
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 a session can be created and destroyed properly.
*
* @see Snapshot: session/should_receive_session_events
*/
@Test
void testShouldReceiveSessionEvents_createAndDestroy() throws Exception {
ctx.configureForTest("session", "should_receive_session_events");
try (CopilotClient client = ctx.createClient()) {
CopilotSession session = client.createSession(new SessionConfig().setModel("fake-test-model")).get();
assertNotNull(session.getSessionId());
assertTrue(session.getSessionId().matches("^[a-f0-9-]+$"));
List messages = session.getMessages().get();
assertFalse(messages.isEmpty());
assertTrue(messages.get(0) instanceof SessionStartEvent);
session.close();
// Session should no longer be accessible - now throws IllegalStateException
try {
session.getMessages().get();
fail("Expected exception for closed session");
} catch (Exception e) {
// After our changes, we now get IllegalStateException directly
String message = e.getMessage();
String causeMessage = e.getCause() != null ? e.getCause().getMessage() : null;
boolean matchesClosed = message != null && message.toLowerCase().contains("closed");
boolean matchesNotFound = causeMessage != null && causeMessage.toLowerCase().contains("not found");
assertTrue(matchesClosed || matchesNotFound);
}
}
}
/**
* Verifies that sessions maintain conversation state across multiple messages.
*
* @see Snapshot: session/should_have_stateful_conversation
*/
@Test
void testShouldHaveStatefulConversation() throws Exception {
ctx.configureForTest("session", "should_have_stateful_conversation");
try (CopilotClient client = ctx.createClient()) {
CopilotSession session = client.createSession().get();
AssistantMessageEvent response1 = session.sendAndWait(new MessageOptions().setPrompt("What is 1+1?"), 60000)
.get(90, TimeUnit.SECONDS);
assertNotNull(response1);
assertTrue(response1.getData().content().contains("2"),
"Response should contain 2: " + response1.getData().content());
AssistantMessageEvent response2 = session
.sendAndWait(new MessageOptions().setPrompt("Now if you double that, what do you get?"), 60000)
.get(90, TimeUnit.SECONDS);
assertNotNull(response2);
assertTrue(response2.getData().content().contains("4"),
"Response should contain 4: " + response2.getData().content());
session.close();
}
}
/**
* Verifies that session events (user.message, assistant.message, session.idle)
* are properly received.
*
* @see Snapshot: session/should_receive_session_events
*/
@Test
void testShouldReceiveSessionEvents() throws Exception {
ctx.configureForTest("session", "should_receive_session_events");
try (CopilotClient client = ctx.createClient()) {
CopilotSession session = client.createSession().get();
List receivedEvents = new ArrayList<>();
CompletableFuture idleReceived = new CompletableFuture<>();
session.on(evt -> {
receivedEvents.add(evt);
if (evt instanceof SessionIdleEvent) {
idleReceived.complete(null);
}
});
session.send(new MessageOptions().setPrompt("What is 100+200?")).get();
idleReceived.get(60, TimeUnit.SECONDS);
assertFalse(receivedEvents.isEmpty());
assertTrue(receivedEvents.stream().anyMatch(e -> e instanceof UserMessageEvent));
assertTrue(receivedEvents.stream().anyMatch(e -> e instanceof AssistantMessageEvent));
assertTrue(receivedEvents.stream().anyMatch(e -> e instanceof SessionIdleEvent));
// Find the assistant message
AssistantMessageEvent assistantMsg = receivedEvents.stream().filter(e -> e instanceof AssistantMessageEvent)
.map(e -> (AssistantMessageEvent) e).findFirst().orElse(null);
assertNotNull(assistantMsg);
assertTrue(assistantMsg.getData().content().contains("300"),
"Response should contain 300: " + assistantMsg.getData().content());
session.close();
}
}
/**
* Verifies that send() returns immediately while events stream in background.
*
* @see Snapshot:
* session/send_returns_immediately_while_events_stream_in_background
*/
@Test
void testSendReturnsImmediatelyWhileEventsStreamInBackground() throws Exception {
ctx.configureForTest("session", "send_returns_immediately_while_events_stream_in_background");
try (CopilotClient client = ctx.createClient()) {
CopilotSession session = client.createSession().get();
var events = new ArrayList();
var lastMessage = new AtomicReference();
var done = new CompletableFuture();
session.on(evt -> {
events.add(evt.getType());
if (evt instanceof AssistantMessageEvent msg) {
lastMessage.set(msg);
} else if (evt instanceof SessionIdleEvent) {
done.complete(null);
}
});
// Use a slow command so we can verify send() returns before completion
// Use String convenience overload (covers send(String) path)
session.send("Run 'sleep 2 && echo done'").get();
// At this point, we might not have received session.idle yet
// The event handling happens asynchronously
// Wait for completion
done.get(60, TimeUnit.SECONDS);
assertTrue(events.contains("session.idle"));
assertTrue(events.contains("assistant.message"));
assertNotNull(lastMessage.get());
assertTrue(lastMessage.get().getData().content().contains("done"),
"Response should contain done: " + lastMessage.get().getData().content());
session.close();
}
}
/**
* Verifies that sendAndWait blocks until session is idle and returns the final
* assistant message.
*
* @see Snapshot:
* session/sendandwait_blocks_until_session_idle_and_returns_final_assistant_message
*/
@Test
void testSendAndWaitBlocksUntilSessionIdleAndReturnsFinalAssistantMessage() throws Exception {
ctx.configureForTest("session", "sendandwait_blocks_until_session_idle_and_returns_final_assistant_message");
try (CopilotClient client = ctx.createClient()) {
CopilotSession session = client.createSession().get();
var events = new ArrayList();
session.on(evt -> events.add(evt.getType()));
// Use String convenience overload (covers sendAndWait(String) path)
AssistantMessageEvent response = session.sendAndWait("What is 2+2?").get(60, TimeUnit.SECONDS);
assertNotNull(response);
assertEquals("assistant.message", response.getType());
assertTrue(response.getData().content().contains("4"),
"Response should contain 4: " + response.getData().content());
assertTrue(events.contains("session.idle"));
assertTrue(events.contains("assistant.message"));
session.close();
}
}
/**
* Verifies that a session can be resumed using the same client.
*
* @see Snapshot: session/should_resume_a_session_using_the_same_client
*/
@Test
void testShouldResumeSessionUsingTheSameClient() throws Exception {
ctx.configureForTest("session", "should_resume_a_session_using_the_same_client");
try (CopilotClient client = ctx.createClient()) {
// Create initial session
CopilotSession session1 = client.createSession().get();
String sessionId = session1.getSessionId();
AssistantMessageEvent answer = session1.sendAndWait(new MessageOptions().setPrompt("What is 1+1?")).get(60,
TimeUnit.SECONDS);
assertNotNull(answer);
assertTrue(answer.getData().content().contains("2"),
"Response should contain 2: " + answer.getData().content());
// Resume using the same client
CopilotSession session2 = client.resumeSession(sessionId).get();
assertEquals(sessionId, session2.getSessionId());
// Verify resumed session has the previous messages
List messages = session2.getMessages().get(60, TimeUnit.SECONDS);
boolean hasAssistantMessage = messages.stream().filter(m -> m instanceof AssistantMessageEvent)
.map(m -> (AssistantMessageEvent) m).anyMatch(m -> m.getData().content().contains("2"));
assertTrue(hasAssistantMessage, "Should find previous assistant message containing 2");
session2.close();
}
}
/**
* Verifies that a session can be resumed using a new client.
*
* @see Snapshot: session/should_resume_a_session_using_a_new_client
*/
@Test
void testShouldResumeSessionUsingNewClient() throws Exception {
ctx.configureForTest("session", "should_resume_a_session_using_a_new_client");
// Use a single try-with-resources for the first client to keep it alive
// throughout the test, matching the behavior of other SDK implementations
try (CopilotClient client1 = ctx.createClient()) {
// Create initial session
CopilotSession session1 = client1.createSession().get();
String sessionId = session1.getSessionId();
AssistantMessageEvent answer = session1.sendAndWait(new MessageOptions().setPrompt("What is 1+1?")).get(60,
TimeUnit.SECONDS);
assertNotNull(answer);
assertTrue(answer.getData().content().contains("2"),
"Response should contain 2: " + answer.getData().content());
// Resume using a new client (keeping client1 alive)
try (CopilotClient client2 = ctx.createClient()) {
CopilotSession session2 = client2.resumeSession(sessionId).get();
assertEquals(sessionId, session2.getSessionId());
// When resuming with a new client, validate messages contain expected types
List messages = session2.getMessages().get(60, TimeUnit.SECONDS);
assertTrue(messages.stream().anyMatch(m -> m instanceof UserMessageEvent),
"Should contain user.message event");
assertTrue(messages.stream().anyMatch(m -> "session.resume".equals(m.getType())),
"Should contain session.resume event");
session2.close();
}
}
}
/**
* Verifies that sessions work with appended system message configuration.
*
* @see Snapshot:
* session/should_create_a_session_with_appended_systemmessage_config
*/
@Test
void testShouldCreateSessionWithAppendedSystemMessageConfig() throws Exception {
ctx.configureForTest("session", "should_create_a_session_with_appended_systemmessage_config");
try (CopilotClient client = ctx.createClient()) {
String systemMessageSuffix = "End each response with the phrase 'Have a nice day!'";
SessionConfig config = new SessionConfig().setSystemMessage(
new SystemMessageConfig().setContent(systemMessageSuffix).setMode(SystemMessageMode.APPEND));
CopilotSession session = client.createSession(config).get();
assertNotNull(session.getSessionId());
AssistantMessageEvent response = session
.sendAndWait(new MessageOptions().setPrompt("What is your full name?")).get(60, TimeUnit.SECONDS);
assertNotNull(response);
assertTrue(response.getData().content().contains("GitHub"),
"Response should contain GitHub: " + response.getData().content());
assertTrue(response.getData().content().contains("Have a nice day!"),
"Response should end with 'Have a nice day!': " + response.getData().content());
session.close();
}
}
/**
* Verifies that sessions work with replaced system message configuration.
*
* @see Snapshot:
* session/should_create_a_session_with_replaced_systemmessage_config
*/
@Test
void testShouldCreateSessionWithReplacedSystemMessageConfig() throws Exception {
ctx.configureForTest("session", "should_create_a_session_with_replaced_systemmessage_config");
try (CopilotClient client = ctx.createClient()) {
String testSystemMessage = "You are an assistant called Testy McTestface. Reply succinctly.";
SessionConfig config = new SessionConfig().setSystemMessage(
new SystemMessageConfig().setContent(testSystemMessage).setMode(SystemMessageMode.REPLACE));
CopilotSession session = client.createSession(config).get();
assertNotNull(session.getSessionId());
AssistantMessageEvent response = session
.sendAndWait(new MessageOptions().setPrompt("What is your full name?")).get(60, TimeUnit.SECONDS);
assertNotNull(response);
assertTrue(response.getData().content().contains("Testy McTestface"),
"Response should contain 'Testy McTestface': " + response.getData().content());
session.close();
}
}
/**
* Verifies that streaming delta events are received when streaming is enabled.
*
* @see Snapshot:
* session/should_receive_streaming_delta_events_when_streaming_is_enabled
*/
@Test
void testShouldReceiveStreamingDeltaEventsWhenStreamingIsEnabled() throws Exception {
ctx.configureForTest("session", "should_receive_streaming_delta_events_when_streaming_is_enabled");
try (CopilotClient client = ctx.createClient()) {
SessionConfig config = new SessionConfig().setStreaming(true);
CopilotSession session = client.createSession(config).get();
var receivedEvents = new ArrayList();
var idleReceived = new CompletableFuture();
session.on(evt -> {
receivedEvents.add(evt);
if (evt instanceof SessionIdleEvent) {
idleReceived.complete(null);
}
});
session.send(new MessageOptions().setPrompt("What is 2+2?")).get();
idleReceived.get(60, TimeUnit.SECONDS);
// Should have received delta events when streaming is enabled
boolean hasDeltaEvents = receivedEvents.stream().anyMatch(e -> e instanceof AssistantMessageDeltaEvent);
assertTrue(hasDeltaEvents, "Should receive streaming delta events when streaming is enabled");
session.close();
}
}
/**
* Verifies that a session can be aborted during tool execution.
*
* @see Snapshot: session/should_abort_a_session
*/
@Test
void testShouldAbortSession() throws Exception {
ctx.configureForTest("session", "should_abort_a_session");
try (CopilotClient client = ctx.createClient()) {
CopilotSession session = client.createSession().get();
assertNotNull(session.getSessionId());
// Set up wait for tool execution to start BEFORE sending
var toolStartFuture = new CompletableFuture();
var sessionIdleFuture = new CompletableFuture();
session.on(evt -> {
if (evt instanceof ToolExecutionStartEvent toolStart && !toolStartFuture.isDone()) {
toolStartFuture.complete(toolStart);
} else if (evt instanceof SessionIdleEvent idle && !sessionIdleFuture.isDone()) {
sessionIdleFuture.complete(idle);
}
});
// Send a message that will trigger a long-running shell command
session.send(new MessageOptions()
.setPrompt("run the shell command 'sleep 100' (note this works on both bash and PowerShell)"))
.get();
// Wait for the tool to start executing
toolStartFuture.get(60, TimeUnit.SECONDS);
// Abort the session while the tool is running
session.abort();
// Wait for session to become idle after abort
sessionIdleFuture.get(30, TimeUnit.SECONDS);
// The session should still be alive and usable after abort
List messages = session.getMessages().get(60, TimeUnit.SECONDS);
assertFalse(messages.isEmpty());
// Verify an abort event exists in messages
assertTrue(messages.stream().anyMatch(m -> m instanceof AbortEvent), "Expected an abort event in messages");
// We should be able to send another message
AssistantMessageEvent answer = session.sendAndWait(new MessageOptions().setPrompt("What is 2+2?")).get(60,
TimeUnit.SECONDS);
assertNotNull(answer);
assertTrue(answer.getData().content().contains("4"),
"Response should contain 4: " + answer.getData().content());
session.close();
}
}
/**
* Verifies that sessions can be created with available tools configuration.
*
* @see Snapshot: session/should_create_a_session_with_availabletools
*/
@Test
void testShouldCreateSessionWithAvailableTools() throws Exception {
ctx.configureForTest("session", "should_create_a_session_with_availabletools");
try (CopilotClient client = ctx.createClient()) {
SessionConfig config = new SessionConfig().setAvailableTools(List.of("view", "edit"));
CopilotSession session = client.createSession(config).get();
assertNotNull(session.getSessionId());
AssistantMessageEvent response = session.sendAndWait(new MessageOptions().setPrompt("What is 1+1?")).get(60,
TimeUnit.SECONDS);
assertNotNull(response);
session.close();
}
}
/**
* Verifies that sessions can be created with excluded tools configuration.
*
* @see Snapshot: session/should_create_a_session_with_excludedtools
*/
@Test
void testShouldCreateSessionWithExcludedTools() throws Exception {
ctx.configureForTest("session", "should_create_a_session_with_excludedtools");
try (CopilotClient client = ctx.createClient()) {
SessionConfig config = new SessionConfig().setExcludedTools(List.of("view"));
CopilotSession session = client.createSession(config).get();
assertNotNull(session.getSessionId());
AssistantMessageEvent response = session.sendAndWait(new MessageOptions().setPrompt("What is 1+1?")).get(60,
TimeUnit.SECONDS);
assertNotNull(response);
assertTrue(response.getData().content().contains("2"),
"Response should contain 2: " + response.getData().content());
session.close();
}
}
/**
* Verifies that an error is thrown when resuming a non-existent session.
*
* @see Snapshot: session/should_receive_session_events
*/
@Test
void testShouldThrowErrorWhenResumingNonExistentSession() throws Exception {
ctx.configureForTest("session", "should_receive_session_events");
try (CopilotClient client = ctx.createClient()) {
try {
client.resumeSession("non-existent-session-id").get(30, TimeUnit.SECONDS);
fail("Expected exception when resuming non-existent session");
} catch (Exception e) {
// Should throw an error
assertTrue(e.getMessage() != null || e.getCause() != null, "Exception should have a message or cause");
}
}
}
/**
* Verifies that sessions can be created with a custom config directory.
*
* @see Snapshot: session/should_create_session_with_custom_config_dir
*/
@Test
void testShouldCreateSessionWithCustomConfigDir() throws Exception {
ctx.configureForTest("session", "should_create_session_with_custom_config_dir");
try (CopilotClient client = ctx.createClient()) {
String customConfigDir = ctx.getWorkDir().resolve("custom-config").toString();
SessionConfig config = new SessionConfig().setConfigDir(customConfigDir);
CopilotSession session = client.createSession(config).get();
assertNotNull(session.getSessionId());
assertTrue(session.getSessionId().matches("^[a-f0-9-]+$"));
// Session should work normally with custom config dir
AssistantMessageEvent response = session.sendAndWait(new MessageOptions().setPrompt("What is 1+1?")).get(60,
TimeUnit.SECONDS);
assertNotNull(response);
assertTrue(response.getData().content().contains("2"),
"Response should contain 2: " + response.getData().content());
session.close();
}
}
// This test validates client-side timeout behavior. The snapshot has no
// assistant response because the test expects timeout BEFORE completion.
// Note: In CI mode, the proxy logs "No cached response found" errors to
// stderr, but these are expected - the timeout still triggers correctly.
/**
* Verifies that sendAndWait throws an exception on timeout.
*
* @see Snapshot: session/sendandwait_throws_on_timeout
*/
@Test
void testSendAndWaitThrowsOnTimeout() throws Exception {
ctx.configureForTest("session", "sendandwait_throws_on_timeout");
try (CopilotClient client = ctx.createClient()) {
CopilotSession session = client.createSession().get();
// Use a short timeout that will trigger before any response
try {
session.sendAndWait(new MessageOptions().setPrompt("Run 'sleep 2 && echo done'"), 100).get(30,
TimeUnit.SECONDS);
fail("Expected timeout exception");
} catch (Exception e) {
// Should throw a timeout-related error from sendAndWait
String message = e.getMessage() != null ? e.getMessage().toLowerCase() : "";
String causeMessage = e.getCause() != null && e.getCause().getMessage() != null
? e.getCause().getMessage().toLowerCase()
: "";
assertTrue(
message.contains("timeout") || message.contains("sendandwait timed out")
|| causeMessage.contains("timeout") || causeMessage.contains("sendandwait timed out"),
"Should throw timeout exception, got: " + e.getMessage()
+ (e.getCause() != null ? " caused by: " + e.getCause().getMessage() : ""));
}
session.close();
}
}
/**
* Verifies that sessions can be listed.
*
* @see Snapshot: session/should_list_sessions
*/
@Test
void testShouldListSessions() throws Exception {
ctx.configureForTest("session", "should_list_sessions");
try (CopilotClient client = ctx.createClient()) {
// Create two sessions and send one message to each (matches snapshot format)
CopilotSession session1 = client.createSession().get();
session1.sendAndWait(new MessageOptions().setPrompt("Say hello")).get(60, TimeUnit.SECONDS);
CopilotSession session2 = client.createSession().get();
session2.sendAndWait(new MessageOptions().setPrompt("Say goodbye")).get(60, TimeUnit.SECONDS);
// Small delay to ensure session files are written to disk
Thread.sleep(200);
// List all sessions
var sessions = client.listSessions().get(30, TimeUnit.SECONDS);
// Should have at least the sessions we created
assertNotNull(sessions);
assertFalse(sessions.isEmpty(), "Should have at least 1 session");
// Our sessions should be in the list
var sessionIds = sessions.stream().map(s -> s.getSessionId()).toList();
assertTrue(sessionIds.contains(session1.getSessionId()), "Session 1 should be in the list");
assertTrue(sessionIds.contains(session2.getSessionId()), "Session 2 should be in the list");
session1.close();
session2.close();
}
}
/**
* Verifies that sessions can be deleted.
*
* @see Snapshot: session/should_delete_session
*/
@Test
void testShouldDeleteSession() throws Exception {
ctx.configureForTest("session", "should_delete_session");
try (CopilotClient client = ctx.createClient()) {
// Create a session
CopilotSession session = client.createSession().get();
String sessionId = session.getSessionId();
session.sendAndWait(new MessageOptions().setPrompt("Hello")).get(60, TimeUnit.SECONDS);
// Delete the session using the client API
// In CI mode with replaying proxy, session files may not be persisted,
// so we handle the "session not found" case as acceptable
try {
client.deleteSession(sessionId).get(30, TimeUnit.SECONDS);
} catch (Exception e) {
// In CI replay mode, session files don't exist - this is expected
if (System.getenv("CI") != null && e.getMessage() != null && e.getMessage().contains("not found")) {
return; // Test passes - CI mode doesn't persist sessions
}
throw e;
}
// Trying to resume the deleted session should fail
try {
client.resumeSession(sessionId).get(30, TimeUnit.SECONDS);
fail("Expected exception when resuming deleted session");
} catch (Exception e) {
// Should throw an error indicating session not found
assertTrue(e.getMessage() != null || e.getCause() != null, "Exception should have a message or cause");
}
}
}
/**
* Verifies that sessions can be created with custom tools.
*
* @see Snapshot: session/should_create_session_with_custom_tool
*/
@Test
void testShouldCreateSessionWithCustomTool() throws Exception {
ctx.configureForTest("session", "should_create_session_with_custom_tool");
// Define a custom get_secret_number tool
Map parameters = new java.util.HashMap<>();
Map properties = new java.util.HashMap<>();
Map keyProp = new java.util.HashMap<>();
keyProp.put("type", "string");
keyProp.put("description", "Key");
properties.put("key", keyProp);
parameters.put("type", "object");
parameters.put("properties", properties);
parameters.put("required", java.util.List.of("key"));
ToolDefinition getSecretNumberTool = ToolDefinition.create("get_secret_number", "Gets the secret number",
parameters, (invocation) -> {
Map args = invocation.getArguments();
String key = (String) args.get("key");
// Return 54321 for ALPHA, 0 otherwise
int result = "ALPHA".equals(key) ? 54321 : 0;
return CompletableFuture.completedFuture(String.valueOf(result));
});
try (CopilotClient client = ctx.createClient()) {
CopilotSession session = client
.createSession(new SessionConfig().setTools(java.util.List.of(getSecretNumberTool))).get();
AssistantMessageEvent response = session
.sendAndWait(new MessageOptions().setPrompt("What is the secret number for key ALPHA?"))
.get(60, TimeUnit.SECONDS);
assertNotNull(response);
assertTrue(response.getData().content().contains("54321"),
"Response should contain 54321: " + response.getData().content());
session.close();
}
}
/**
* Verifies that streaming option is passed to session creation.
*
* @see Snapshot: session/should_pass_streaming_option_to_session_creation
*/
@Test
void testShouldPassStreamingOptionToSessionCreation() throws Exception {
ctx.configureForTest("session", "should_pass_streaming_option_to_session_creation");
try (CopilotClient client = ctx.createClient()) {
// Verify that the streaming option is accepted without errors
CopilotSession session = client.createSession(new SessionConfig().setStreaming(true)).get();
assertNotNull(session.getSessionId());
assertTrue(session.getSessionId().matches("^[a-f0-9-]+$"));
// Session should still work normally
AssistantMessageEvent response = session.sendAndWait(new MessageOptions().setPrompt("What is 1+1?")).get(60,
TimeUnit.SECONDS);
assertNotNull(response);
assertTrue(response.getData().content().contains("2"),
"Response should contain 2: " + response.getData().content());
session.close();
}
}
}