Skip to content

Commit face1a7

Browse files
authored
Merge pull request #8 from copilot-community-sdk/merge-upstream-20260203
Merge upstream 20260203
2 parents 98d9013 + 07b8b3f commit face1a7

9 files changed

Lines changed: 114 additions & 27 deletions

File tree

.lastmerge

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
e6e4decda7bdc74cb669cb1da6fdf45be7151034
1+
73c14315ede2a2f9329fe2a442cd220a80f14191

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build A
1515
### Requirements
1616

1717
- Java 17 or later
18-
- GitHub Copilot CLI 0.0.400 or later installed and in PATH (or provide custom `cliPath`)
18+
- GitHub Copilot CLI 0.0.401 or later installed and in PATH (or provide custom `cliPath`)
1919

2020
### Maven
2121

pom.xml

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,28 +106,39 @@
106106
</goals>
107107
<configuration>
108108
<target xmlns:if="ant:if" xmlns:unless="ant:unless">
109+
<!-- Load the target commit from .lastmerge file -->
110+
<loadfile property="copilot.sdk.commit" srcFile="${project.basedir}/.lastmerge">
111+
<filterchain>
112+
<striplinebreaks/>
113+
<trim/>
114+
</filterchain>
115+
</loadfile>
116+
109117
<!-- Check if .git directory exists -->
110118
<condition property="repo.exists">
111119
<available file="${copilot.sdk.clone.dir}/.git" type="dir" />
112120
</condition>
113121

114-
<!-- If repo exists, fetch and reset -->
122+
<!-- If repo exists, fetch and reset to the target commit -->
115123
<sequential if:set="repo.exists">
116-
<echo message="Updating existing copilot-sdk repository..." />
124+
<echo message="Updating existing copilot-sdk repository to commit ${copilot.sdk.commit}..." />
117125
<exec executable="git" dir="${copilot.sdk.clone.dir}" failonerror="true">
118126
<arg value="fetch" />
127+
<arg value="--depth" />
128+
<arg value="1" />
119129
<arg value="origin" />
130+
<arg value="${copilot.sdk.commit}" />
120131
</exec>
121132
<exec executable="git" dir="${copilot.sdk.clone.dir}" failonerror="true">
122133
<arg value="reset" />
123134
<arg value="--hard" />
124-
<arg value="origin/main" />
135+
<arg value="FETCH_HEAD" />
125136
</exec>
126137
</sequential>
127138

128-
<!-- If repo doesn't exist, clone it -->
139+
<!-- If repo doesn't exist, clone it at the specific commit -->
129140
<sequential unless:set="repo.exists">
130-
<echo message="Cloning copilot-sdk repository..." />
141+
<echo message="Cloning copilot-sdk repository at commit ${copilot.sdk.commit}..." />
131142
<delete dir="${copilot.sdk.clone.dir}" quiet="true" />
132143
<exec executable="git" failonerror="true">
133144
<arg value="clone" />
@@ -136,6 +147,18 @@
136147
<arg value="https://github.com/github/copilot-sdk.git" />
137148
<arg value="${copilot.sdk.clone.dir}" />
138149
</exec>
150+
<exec executable="git" dir="${copilot.sdk.clone.dir}" failonerror="true">
151+
<arg value="fetch" />
152+
<arg value="--depth" />
153+
<arg value="1" />
154+
<arg value="origin" />
155+
<arg value="${copilot.sdk.commit}" />
156+
</exec>
157+
<exec executable="git" dir="${copilot.sdk.clone.dir}" failonerror="true">
158+
<arg value="reset" />
159+
<arg value="--hard" />
160+
<arg value="FETCH_HEAD" />
161+
</exec>
139162
</sequential>
140163
</target>
141164
</configuration>

src/site/markdown/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Welcome to the documentation for the **Copilot SDK for Java** — a Java SDK for
99
### Requirements
1010

1111
- Java 17 or later
12-
- GitHub Copilot CLI 0.0.400 or later installed and in PATH (or provide custom `cliPath`)
12+
- GitHub Copilot CLI 0.0.401 or later installed and in PATH (or provide custom `cliPath`)
1313

1414
### Installation
1515

src/test/java/com/github/copilot/sdk/AskUserTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
*
2626
* <p>
2727
* These tests use the shared CapiProxy infrastructure for deterministic API
28-
* response replay. Snapshots are stored in test/snapshots/ask-user/.
28+
* response replay. Snapshots are stored in test/snapshots/ask_user/.
2929
* </p>
3030
*/
3131
public class AskUserTest {
@@ -46,7 +46,7 @@ static void teardown() throws Exception {
4646

4747
@Test
4848
void testUserInputHandlerInvokedWhenModelUsesAskUserTool() throws Exception {
49-
ctx.configureForTest("ask-user", "should_invoke_user_input_handler_when_model_uses_ask_user_tool");
49+
ctx.configureForTest("ask_user", "should_invoke_user_input_handler_when_model_uses_ask_user_tool");
5050

5151
List<UserInputRequest> userInputRequests = new ArrayList<>();
5252
final String[] sessionIdHolder = new String[1];
@@ -84,7 +84,7 @@ void testUserInputHandlerInvokedWhenModelUsesAskUserTool() throws Exception {
8484

8585
@Test
8686
void testUserInputRequestWithChoices() throws Exception {
87-
ctx.configureForTest("ask-user", "should_receive_choices_in_user_input_request");
87+
ctx.configureForTest("ask_user", "should_receive_choices_in_user_input_request");
8888

8989
List<UserInputRequest> userInputRequests = new ArrayList<>();
9090

@@ -120,7 +120,7 @@ void testUserInputRequestWithChoices() throws Exception {
120120
*/
121121
@Test
122122
void testFreeformUserInputResponse() throws Exception {
123-
ctx.configureForTest("ask-user", "should_handle_freeform_user_input_response");
123+
ctx.configureForTest("ask_user", "should_handle_freeform_user_input_response");
124124

125125
final List<UserInputRequest> userInputRequests = new ArrayList<>();
126126
String freeformAnswer = "This is my custom freeform answer that was not in the choices";

src/test/java/com/github/copilot/sdk/CapiProxy.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,39 @@ public String getProxyUrl() {
288288
return proxyUrl;
289289
}
290290

291+
/**
292+
* Checks if the proxy process is still alive.
293+
*
294+
* @return true if the proxy is running, false otherwise
295+
*/
296+
public boolean isAlive() {
297+
return process != null && process.isAlive();
298+
}
299+
300+
/**
301+
* Restarts the proxy server. This stops the current instance (if any) and
302+
* starts a new one.
303+
*
304+
* @return the new proxy URL
305+
* @throws IOException
306+
* if the server fails to start
307+
* @throws InterruptedException
308+
* if the startup is interrupted
309+
*/
310+
public String restart() throws IOException, InterruptedException {
311+
try {
312+
stop(true); // Skip writing cache on restart
313+
} catch (Exception e) {
314+
// Best effort - force cleanup
315+
if (process != null) {
316+
process.destroyForcibly();
317+
process = null;
318+
}
319+
proxyUrl = null;
320+
}
321+
return start();
322+
}
323+
291324
@Override
292325
public void close() throws Exception {
293326
stop();

src/test/java/com/github/copilot/sdk/CopilotSessionTest.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.junit.jupiter.api.AfterAll;
2020
import org.junit.jupiter.api.BeforeAll;
2121
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;
2223

2324
import com.github.copilot.sdk.events.AbstractSessionEvent;
2425
import com.github.copilot.sdk.events.AbortEvent;
@@ -486,17 +487,22 @@ void testCreateSessionWithCustomConfigDir() throws Exception {
486487
}
487488
}
488489

490+
// Skip in CI - this test validates client-side timeout behavior, not LLM
491+
// responses.
492+
// The test intentionally times out before receiving a response, so there's no
493+
// snapshot to replay.
489494
@Test
495+
@DisabledIfEnvironmentVariable(named = "CI", matches = ".*")
490496
void testSendAndWaitThrowsOnTimeout() throws Exception {
491-
ctx.configureForTest("session", "should_receive_session_events");
497+
ctx.configureForTest("session", "sendandwait_throws_on_timeout");
492498

493499
try (CopilotClient client = ctx.createClient()) {
494500
CopilotSession session = client.createSession().get();
495501

496502
// Use a very short timeout that will definitely expire
497503
try {
498504
// Note: We use a command that takes time so timeout triggers before completion
499-
session.sendAndWait(new MessageOptions().setPrompt("Run 'sleep 10 && echo done'"), 100).get(5,
505+
session.sendAndWait(new MessageOptions().setPrompt("Run 'sleep 2 && echo done'"), 100).get(5,
500506
TimeUnit.SECONDS);
501507
fail("Expected timeout exception");
502508
} catch (Exception e) {

src/test/java/com/github/copilot/sdk/E2ETestContext.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public class E2ETestContext implements AutoCloseable {
5555
private final String cliPath;
5656
private final Path homeDir;
5757
private final Path workDir;
58-
private final String proxyUrl;
58+
private String proxyUrl;
5959
private final CapiProxy proxy;
6060
private final Path repoRoot;
6161

@@ -133,6 +133,9 @@ public String getProxyUrl() {
133133
* if configuration is interrupted
134134
*/
135135
public void configureForTest(String testFile, String testName) throws IOException, InterruptedException {
136+
// Restart the proxy if it has crashed
137+
ensureProxyAlive();
138+
136139
// Convert test method names to lowercase snake_case for snapshot filenames
137140
// to avoid case collisions on case-insensitive filesystems (macOS/Windows)
138141
String sanitizedName = SNAKE_CASE.matcher(testName).replaceAll("_").toLowerCase();
@@ -141,6 +144,20 @@ public void configureForTest(String testFile, String testName) throws IOExceptio
141144
proxy.configure(snapshotPath, workDir.toString());
142145
}
143146

147+
/**
148+
* Ensures the proxy is alive, restarting it if necessary.
149+
*
150+
* @throws IOException
151+
* if the proxy cannot be restarted
152+
* @throws InterruptedException
153+
* if interrupted during restart
154+
*/
155+
public void ensureProxyAlive() throws IOException, InterruptedException {
156+
if (!proxy.isAlive()) {
157+
proxyUrl = proxy.restart();
158+
}
159+
}
160+
144161
/**
145162
* Gets the captured HTTP exchanges from the proxy.
146163
*
@@ -173,8 +190,16 @@ public Map<String, String> getEnvironment() {
173190
* @return a new CopilotClient
174191
*/
175192
public CopilotClient createClient() {
176-
return new CopilotClient(new CopilotClientOptions().setCliPath(cliPath).setCwd(workDir.toString())
177-
.setEnvironment(getEnvironment()));
193+
CopilotClientOptions options = new CopilotClientOptions().setCliPath(cliPath).setCwd(workDir.toString())
194+
.setEnvironment(getEnvironment());
195+
196+
// In CI, use a fake token to avoid auth issues
197+
String ci = System.getenv("CI");
198+
if (ci != null && !ci.isEmpty()) {
199+
options.setGithubToken("fake-token-for-e2e-tests");
200+
}
201+
202+
return new CopilotClient(options);
178203
}
179204

180205
@Override

src/test/java/com/github/copilot/sdk/McpAndAgentsTest.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
*
2727
* <p>
2828
* These tests use the shared CapiProxy infrastructure for deterministic API
29-
* response replay. Snapshots are stored in test/snapshots/mcp-and-agents/.
29+
* response replay. Snapshots are stored in test/snapshots/mcp_and_agents/.
3030
* </p>
3131
*/
3232
public class McpAndAgentsTest {
@@ -59,7 +59,7 @@ private Map<String, Object> createLocalMcpServer(String command, List<String> ar
5959

6060
@Test
6161
void testAcceptMcpServerConfigurationOnSessionCreate() throws Exception {
62-
ctx.configureForTest("mcp-and-agents", "should_accept_mcp_server_configuration_on_session_create");
62+
ctx.configureForTest("mcp_and_agents", "should_accept_mcp_server_configuration_on_session_create");
6363

6464
Map<String, Object> mcpServers = new HashMap<>();
6565
mcpServers.put("test-server", createLocalMcpServer("echo", List.of("hello")));
@@ -83,7 +83,7 @@ void testAcceptMcpServerConfigurationOnSessionCreate() throws Exception {
8383

8484
@Test
8585
void testAcceptMcpServerConfigurationOnSessionResume() throws Exception {
86-
ctx.configureForTest("mcp-and-agents", "should_accept_mcp_server_configuration_on_session_resume");
86+
ctx.configureForTest("mcp_and_agents", "should_accept_mcp_server_configuration_on_session_resume");
8787

8888
try (CopilotClient client = ctx.createClient()) {
8989
// Create a session first
@@ -115,7 +115,7 @@ void testAcceptMcpServerConfigurationOnSessionResume() throws Exception {
115115
void testHandleMultipleMcpServers() throws Exception {
116116
// Use same snapshot as single MCP server test since it doesn't depend on server
117117
// count
118-
ctx.configureForTest("mcp-and-agents", "should_accept_mcp_server_configuration_on_session_create");
118+
ctx.configureForTest("mcp_and_agents", "should_accept_mcp_server_configuration_on_session_create");
119119

120120
Map<String, Object> mcpServers = new HashMap<>();
121121
mcpServers.put("server1", createLocalMcpServer("echo", List.of("server1")));
@@ -133,7 +133,7 @@ void testHandleMultipleMcpServers() throws Exception {
133133

134134
@Test
135135
void testAcceptCustomAgentConfigurationOnSessionCreate() throws Exception {
136-
ctx.configureForTest("mcp-and-agents", "should_accept_custom_agent_configuration_on_session_create");
136+
ctx.configureForTest("mcp_and_agents", "should_accept_custom_agent_configuration_on_session_create");
137137

138138
List<CustomAgentConfig> customAgents = List.of(new CustomAgentConfig().setName("test-agent")
139139
.setDisplayName("Test Agent").setDescription("A test agent for SDK testing")
@@ -158,7 +158,7 @@ void testAcceptCustomAgentConfigurationOnSessionCreate() throws Exception {
158158

159159
@Test
160160
void testAcceptCustomAgentConfigurationOnSessionResume() throws Exception {
161-
ctx.configureForTest("mcp-and-agents", "should_accept_custom_agent_configuration_on_session_resume");
161+
ctx.configureForTest("mcp_and_agents", "should_accept_custom_agent_configuration_on_session_resume");
162162

163163
try (CopilotClient client = ctx.createClient()) {
164164
// Create a session first
@@ -191,7 +191,7 @@ void testAcceptCustomAgentConfigurationOnSessionResume() throws Exception {
191191
void testCustomAgentWithToolsConfiguration() throws Exception {
192192
// Use same snapshot as create test since this just verifies configuration
193193
// acceptance
194-
ctx.configureForTest("mcp-and-agents", "should_accept_custom_agent_configuration_on_session_create");
194+
ctx.configureForTest("mcp_and_agents", "should_accept_custom_agent_configuration_on_session_create");
195195

196196
List<CustomAgentConfig> customAgents = List.of(new CustomAgentConfig().setName("tool-agent")
197197
.setDisplayName("Tool Agent").setDescription("An agent with specific tools")
@@ -208,7 +208,7 @@ void testCustomAgentWithToolsConfiguration() throws Exception {
208208
@Test
209209
void testCustomAgentWithMcpServers() throws Exception {
210210
// Use combined snapshot since this uses both MCP servers and custom agents
211-
ctx.configureForTest("mcp-and-agents", "should_accept_both_mcp_servers_and_custom_agents");
211+
ctx.configureForTest("mcp_and_agents", "should_accept_both_mcp_servers_and_custom_agents");
212212

213213
Map<String, Object> agentMcpServers = new HashMap<>();
214214
agentMcpServers.put("agent-server", createLocalMcpServer("echo", List.of("agent-mcp")));
@@ -228,7 +228,7 @@ void testCustomAgentWithMcpServers() throws Exception {
228228
@Test
229229
void testMultipleCustomAgents() throws Exception {
230230
// Use same snapshot as create test
231-
ctx.configureForTest("mcp-and-agents", "should_accept_custom_agent_configuration_on_session_create");
231+
ctx.configureForTest("mcp_and_agents", "should_accept_custom_agent_configuration_on_session_create");
232232

233233
List<CustomAgentConfig> customAgents = List.of(
234234
new CustomAgentConfig().setName("agent1").setDisplayName("Agent One").setDescription("First agent")
@@ -248,7 +248,7 @@ void testMultipleCustomAgents() throws Exception {
248248

249249
@Test
250250
void testAcceptBothMcpServersAndCustomAgents() throws Exception {
251-
ctx.configureForTest("mcp-and-agents", "should_accept_both_mcp_servers_and_custom_agents");
251+
ctx.configureForTest("mcp_and_agents", "should_accept_both_mcp_servers_and_custom_agents");
252252

253253
Map<String, Object> mcpServers = new HashMap<>();
254254
mcpServers.put("shared-server", createLocalMcpServer("echo", List.of("shared")));

0 commit comments

Comments
 (0)