From 1af066aa2f8278c6867bf14f406c1e189520bc0a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 09:32:54 +0000 Subject: [PATCH 01/12] Initial plan From 21f956026f0e37f46a3eabcc4884a2f381c5d4b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 09:41:30 +0000 Subject: [PATCH 02/12] Add clone() methods to config classes Port upstream commit ce54e43: Add copy constructors and Clone methods to the various .NET config option bags (#422) Added clone() methods to: - CopilotClientOptions - SessionConfig - ResumeSessionConfig - MessageOptions These methods create shallow clones where mutable collection properties (lists, maps, arrays) are copied into new instances to ensure independence, while other reference-type properties are shared. Also added comprehensive tests in ConfigCloneTest to verify the clone behavior. Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --- .../github/copilot/sdk/CliServerManager.java | 7 ++ .../sdk/json/CopilotClientOptions.java | 27 ++++ .../copilot/sdk/json/MessageOptions.java | 19 +++ .../copilot/sdk/json/ResumeSessionConfig.java | 36 ++++++ .../copilot/sdk/json/SessionConfig.java | 36 ++++++ .../github/copilot/sdk/ConfigCloneTest.java | 118 ++++++++++++++++++ 6 files changed, 243 insertions(+) create mode 100644 src/test/java/com/github/copilot/sdk/ConfigCloneTest.java diff --git a/src/main/java/com/github/copilot/sdk/CliServerManager.java b/src/main/java/com/github/copilot/sdk/CliServerManager.java index 1ac43c719..f4f570d49 100644 --- a/src/main/java/com/github/copilot/sdk/CliServerManager.java +++ b/src/main/java/com/github/copilot/sdk/CliServerManager.java @@ -85,6 +85,13 @@ ProcessInfo startCliServer() throws IOException, InterruptedException { var pb = new ProcessBuilder(command); pb.redirectErrorStream(false); + // Note: On Windows, console window visibility depends on how the parent Java + // process was launched. GUI applications started with 'javaw' will not create + // visible console windows for subprocesses. Console applications started with + // 'java' will share their console with subprocesses. Java's ProcessBuilder + // doesn't provide explicit CREATE_NO_WINDOW flags like native Windows APIs, + // but the default behavior is appropriate for most use cases. + if (options.getCwd() != null) { pb.directory(new File(options.getCwd())); } diff --git a/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java b/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java index b51ab7a19..05945edc3 100644 --- a/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java +++ b/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java @@ -323,4 +323,31 @@ public CopilotClientOptions setUseLoggedInUser(Boolean useLoggedInUser) { this.useLoggedInUser = useLoggedInUser; return this; } + + /** + * Creates a shallow clone of this {@code CopilotClientOptions} instance. + *

+ * Array properties (like {@code cliArgs}) are copied into new arrays so that + * modifications to the clone do not affect the original. Other reference-type + * properties (like {@code environment}) are shared between the original and + * clone. + * + * @return a clone of this options instance + */ + public CopilotClientOptions clone() { + CopilotClientOptions copy = new CopilotClientOptions(); + copy.cliPath = this.cliPath; + copy.cliArgs = this.cliArgs != null ? this.cliArgs.clone() : null; + copy.cwd = this.cwd; + copy.port = this.port; + copy.useStdio = this.useStdio; + copy.cliUrl = this.cliUrl; + copy.logLevel = this.logLevel; + copy.autoStart = this.autoStart; + copy.autoRestart = this.autoRestart; + copy.environment = this.environment; + copy.githubToken = this.githubToken; + copy.useLoggedInUser = this.useLoggedInUser; + return copy; + } } diff --git a/src/main/java/com/github/copilot/sdk/json/MessageOptions.java b/src/main/java/com/github/copilot/sdk/json/MessageOptions.java index 99c4214b8..4302b213e 100644 --- a/src/main/java/com/github/copilot/sdk/json/MessageOptions.java +++ b/src/main/java/com/github/copilot/sdk/json/MessageOptions.java @@ -4,6 +4,7 @@ package com.github.copilot.sdk.json; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -107,4 +108,22 @@ public String getMode() { return mode; } + /** + * Creates a shallow clone of this {@code MessageOptions} instance. + *

+ * Mutable collection properties are copied into new collection instances so + * that modifications to those collections on the clone do not affect the + * original. Other reference-type properties (like attachment items) are not + * deep-cloned; the original and the clone will share those objects. + * + * @return a clone of this options instance + */ + public MessageOptions clone() { + MessageOptions copy = new MessageOptions(); + copy.prompt = this.prompt; + copy.attachments = this.attachments != null ? new ArrayList<>(this.attachments) : null; + copy.mode = this.mode; + return copy; + } + } diff --git a/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java b/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java index 8b7d841f8..a75c90d0a 100644 --- a/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java +++ b/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java @@ -4,6 +4,7 @@ package com.github.copilot.sdk.json; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -475,4 +476,39 @@ public ResumeSessionConfig setInfiniteSessions(InfiniteSessionConfig infiniteSes this.infiniteSessions = infiniteSessions; return this; } + + /** + * Creates a shallow clone of this {@code ResumeSessionConfig} instance. + *

+ * Mutable collection properties are copied into new collection instances so + * that modifications to those collections on the clone do not affect the + * original. Other reference-type properties (like provider configuration, + * system messages, hooks, infinite session configuration, and delegates) are + * not deep-cloned; the original and the clone will share those objects. + * + * @return a clone of this config instance + */ + public ResumeSessionConfig clone() { + ResumeSessionConfig copy = new ResumeSessionConfig(); + copy.model = this.model; + copy.tools = this.tools != null ? new ArrayList<>(this.tools) : null; + copy.systemMessage = this.systemMessage; + copy.availableTools = this.availableTools != null ? new ArrayList<>(this.availableTools) : null; + copy.excludedTools = this.excludedTools != null ? new ArrayList<>(this.excludedTools) : null; + copy.provider = this.provider; + copy.reasoningEffort = this.reasoningEffort; + copy.onPermissionRequest = this.onPermissionRequest; + copy.onUserInputRequest = this.onUserInputRequest; + copy.hooks = this.hooks; + copy.workingDirectory = this.workingDirectory; + copy.configDir = this.configDir; + copy.disableResume = this.disableResume; + copy.streaming = this.streaming; + copy.mcpServers = this.mcpServers != null ? new java.util.HashMap<>(this.mcpServers) : null; + copy.customAgents = this.customAgents != null ? new ArrayList<>(this.customAgents) : null; + copy.skillDirectories = this.skillDirectories != null ? new ArrayList<>(this.skillDirectories) : null; + copy.disabledSkills = this.disabledSkills != null ? new ArrayList<>(this.disabledSkills) : null; + copy.infiniteSessions = this.infiniteSessions; + return copy; + } } diff --git a/src/main/java/com/github/copilot/sdk/json/SessionConfig.java b/src/main/java/com/github/copilot/sdk/json/SessionConfig.java index 9d7e7367f..34aaab7bd 100644 --- a/src/main/java/com/github/copilot/sdk/json/SessionConfig.java +++ b/src/main/java/com/github/copilot/sdk/json/SessionConfig.java @@ -4,6 +4,7 @@ package com.github.copilot.sdk.json; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -512,4 +513,39 @@ public SessionConfig setConfigDir(String configDir) { this.configDir = configDir; return this; } + + /** + * Creates a shallow clone of this {@code SessionConfig} instance. + *

+ * Mutable collection properties are copied into new collection instances so + * that modifications to those collections on the clone do not affect the + * original. Other reference-type properties (like provider configuration, + * system messages, hooks, infinite session configuration, and delegates) are + * not deep-cloned; the original and the clone will share those objects. + * + * @return a clone of this config instance + */ + public SessionConfig clone() { + SessionConfig copy = new SessionConfig(); + copy.sessionId = this.sessionId; + copy.model = this.model; + copy.reasoningEffort = this.reasoningEffort; + copy.tools = this.tools != null ? new ArrayList<>(this.tools) : null; + copy.systemMessage = this.systemMessage; + copy.availableTools = this.availableTools != null ? new ArrayList<>(this.availableTools) : null; + copy.excludedTools = this.excludedTools != null ? new ArrayList<>(this.excludedTools) : null; + copy.provider = this.provider; + copy.onPermissionRequest = this.onPermissionRequest; + copy.onUserInputRequest = this.onUserInputRequest; + copy.hooks = this.hooks; + copy.workingDirectory = this.workingDirectory; + copy.streaming = this.streaming; + copy.mcpServers = this.mcpServers != null ? new java.util.HashMap<>(this.mcpServers) : null; + copy.customAgents = this.customAgents != null ? new ArrayList<>(this.customAgents) : null; + copy.infiniteSessions = this.infiniteSessions; + copy.skillDirectories = this.skillDirectories != null ? new ArrayList<>(this.skillDirectories) : null; + copy.disabledSkills = this.disabledSkills != null ? new ArrayList<>(this.disabledSkills) : null; + copy.configDir = this.configDir; + return copy; + } } diff --git a/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java b/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java new file mode 100644 index 000000000..6c834edbb --- /dev/null +++ b/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java @@ -0,0 +1,118 @@ +/*--------------------------------------------------------------------------------------------- + * 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 org.junit.jupiter.api.Test; + +import com.github.copilot.sdk.json.CopilotClientOptions; +import com.github.copilot.sdk.json.SessionConfig; +import com.github.copilot.sdk.json.ResumeSessionConfig; +import com.github.copilot.sdk.json.MessageOptions; + +class ConfigCloneTest { + + @Test + void copilotClientOptionsCloneBasic() { + CopilotClientOptions original = new CopilotClientOptions(); + original.setCliPath("/usr/local/bin/copilot"); + original.setLogLevel("debug"); + original.setPort(9000); + + CopilotClientOptions cloned = original.clone(); + + assertEquals(original.getCliPath(), cloned.getCliPath()); + assertEquals(original.getLogLevel(), cloned.getLogLevel()); + assertEquals(original.getPort(), cloned.getPort()); + } + + @Test + void copilotClientOptionsArrayIndependence() { + CopilotClientOptions original = new CopilotClientOptions(); + String[] args = {"--flag1", "--flag2"}; + original.setCliArgs(args); + + CopilotClientOptions cloned = original.clone(); + cloned.getCliArgs()[0] = "--changed"; + + assertEquals("--flag1", original.getCliArgs()[0]); + assertEquals("--changed", cloned.getCliArgs()[0]); + } + + @Test + void sessionConfigCloneBasic() { + SessionConfig original = new SessionConfig(); + original.setSessionId("my-session"); + original.setModel("gpt-4o"); + original.setStreaming(true); + + SessionConfig cloned = original.clone(); + + assertEquals(original.getSessionId(), cloned.getSessionId()); + assertEquals(original.getModel(), cloned.getModel()); + assertEquals(original.isStreaming(), cloned.isStreaming()); + } + + @Test + void sessionConfigListIndependence() { + SessionConfig original = new SessionConfig(); + List toolList = new ArrayList<>(); + toolList.add("grep"); + toolList.add("bash"); + original.setAvailableTools(toolList); + + SessionConfig cloned = original.clone(); + + List clonedTools = new ArrayList<>(cloned.getAvailableTools()); + clonedTools.add("web"); + cloned.setAvailableTools(clonedTools); + + assertEquals(2, original.getAvailableTools().size()); + assertEquals(3, cloned.getAvailableTools().size()); + } + + @Test + void resumeSessionConfigCloneBasic() { + ResumeSessionConfig original = new ResumeSessionConfig(); + original.setModel("o1"); + original.setStreaming(false); + + ResumeSessionConfig cloned = original.clone(); + + assertEquals(original.getModel(), cloned.getModel()); + assertEquals(original.isStreaming(), cloned.isStreaming()); + } + + @Test + void messageOptionsCloneBasic() { + MessageOptions original = new MessageOptions(); + original.setPrompt("What is 2+2?"); + original.setMode("immediate"); + + MessageOptions cloned = original.clone(); + + assertEquals(original.getPrompt(), cloned.getPrompt()); + assertEquals(original.getMode(), cloned.getMode()); + } + + @Test + void clonePreservesNullFields() { + CopilotClientOptions opts = new CopilotClientOptions(); + CopilotClientOptions optsClone = opts.clone(); + assertNull(optsClone.getCliPath()); + + SessionConfig cfg = new SessionConfig(); + SessionConfig cfgClone = cfg.clone(); + assertNull(cfgClone.getModel()); + + MessageOptions msg = new MessageOptions(); + MessageOptions msgClone = msg.clone(); + assertNull(msgClone.getMode()); + } +} From 4bf82ebec4fbd7c647386cd30e07e110d4b6126b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 09:41:50 +0000 Subject: [PATCH 03/12] Update .lastmerge to 5016587 Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --- .lastmerge | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.lastmerge b/.lastmerge index 04846a767..2e1ed67d3 100644 --- a/.lastmerge +++ b/.lastmerge @@ -1 +1 @@ -304d812cd4c98755159da427c6701bfb7e0b7c32 +5016587a62652f3d184b3c6958dfc63359921aa8 From 962f97bd1db9646436ac9cc33b0a48143afcf596 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 17 Feb 2026 08:31:09 -0500 Subject: [PATCH 04/12] Update src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../java/com/github/copilot/sdk/json/ResumeSessionConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java b/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java index a75c90d0a..e58ae3ebb 100644 --- a/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java +++ b/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java @@ -488,6 +488,7 @@ public ResumeSessionConfig setInfiniteSessions(InfiniteSessionConfig infiniteSes * * @return a clone of this config instance */ + @Override public ResumeSessionConfig clone() { ResumeSessionConfig copy = new ResumeSessionConfig(); copy.model = this.model; From 8dbd48fce39d7ad978c3f4c2dafbb67d39040a3f Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 17 Feb 2026 08:31:32 -0500 Subject: [PATCH 05/12] Update src/main/java/com/github/copilot/sdk/json/SessionConfig.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/java/com/github/copilot/sdk/json/SessionConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/github/copilot/sdk/json/SessionConfig.java b/src/main/java/com/github/copilot/sdk/json/SessionConfig.java index 34aaab7bd..93ab81bda 100644 --- a/src/main/java/com/github/copilot/sdk/json/SessionConfig.java +++ b/src/main/java/com/github/copilot/sdk/json/SessionConfig.java @@ -520,7 +520,7 @@ public SessionConfig setConfigDir(String configDir) { * Mutable collection properties are copied into new collection instances so * that modifications to those collections on the clone do not affect the * original. Other reference-type properties (like provider configuration, - * system messages, hooks, infinite session configuration, and delegates) are + * system messages, hooks, infinite session configuration, and handlers) are * not deep-cloned; the original and the clone will share those objects. * * @return a clone of this config instance From bd64fb81f9511b2ded1f57ef84d0164f0c2016d3 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 17 Feb 2026 08:31:43 -0500 Subject: [PATCH 06/12] Update src/main/java/com/github/copilot/sdk/json/MessageOptions.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/java/com/github/copilot/sdk/json/MessageOptions.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/github/copilot/sdk/json/MessageOptions.java b/src/main/java/com/github/copilot/sdk/json/MessageOptions.java index 4302b213e..6ba629b7b 100644 --- a/src/main/java/com/github/copilot/sdk/json/MessageOptions.java +++ b/src/main/java/com/github/copilot/sdk/json/MessageOptions.java @@ -118,6 +118,7 @@ public String getMode() { * * @return a clone of this options instance */ + @Override public MessageOptions clone() { MessageOptions copy = new MessageOptions(); copy.prompt = this.prompt; From 64ab5c195959ed05e569f9122f6bbeecd3ae003d Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 17 Feb 2026 09:40:54 -0500 Subject: [PATCH 07/12] Potential fix for pull request finding 'Missing Override annotation' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- .../java/com/github/copilot/sdk/json/CopilotClientOptions.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java b/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java index 05945edc3..28a75bc6f 100644 --- a/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java +++ b/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java @@ -334,6 +334,7 @@ public CopilotClientOptions setUseLoggedInUser(Boolean useLoggedInUser) { * * @return a clone of this options instance */ + @Override public CopilotClientOptions clone() { CopilotClientOptions copy = new CopilotClientOptions(); copy.cliPath = this.cliPath; From 003d5f7e41601ac8a31f6c7c5df35109ec3c5c7e Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 17 Feb 2026 09:44:53 -0500 Subject: [PATCH 08/12] Update src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../java/com/github/copilot/sdk/json/CopilotClientOptions.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java b/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java index 28a75bc6f..89b196dd8 100644 --- a/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java +++ b/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java @@ -335,6 +335,7 @@ public CopilotClientOptions setUseLoggedInUser(Boolean useLoggedInUser) { * @return a clone of this options instance */ @Override + @Override public CopilotClientOptions clone() { CopilotClientOptions copy = new CopilotClientOptions(); copy.cliPath = this.cliPath; From 5cf778bb0aa9b404fa1a7c27d84049e1564bf252 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 17 Feb 2026 09:45:13 -0500 Subject: [PATCH 09/12] Update src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../java/com/github/copilot/sdk/json/ResumeSessionConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java b/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java index e58ae3ebb..182467c23 100644 --- a/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java +++ b/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java @@ -483,7 +483,7 @@ public ResumeSessionConfig setInfiniteSessions(InfiniteSessionConfig infiniteSes * Mutable collection properties are copied into new collection instances so * that modifications to those collections on the clone do not affect the * original. Other reference-type properties (like provider configuration, - * system messages, hooks, infinite session configuration, and delegates) are + * system messages, hooks, infinite session configuration, and handlers) are * not deep-cloned; the original and the clone will share those objects. * * @return a clone of this config instance From 7bbabef8cc2fa613977b25b49f2e2585e23cc828 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 17 Feb 2026 09:45:32 -0500 Subject: [PATCH 10/12] Update src/main/java/com/github/copilot/sdk/json/SessionConfig.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/java/com/github/copilot/sdk/json/SessionConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/github/copilot/sdk/json/SessionConfig.java b/src/main/java/com/github/copilot/sdk/json/SessionConfig.java index 93ab81bda..6b29c5bb4 100644 --- a/src/main/java/com/github/copilot/sdk/json/SessionConfig.java +++ b/src/main/java/com/github/copilot/sdk/json/SessionConfig.java @@ -525,6 +525,7 @@ public SessionConfig setConfigDir(String configDir) { * * @return a clone of this config instance */ + @Override public SessionConfig clone() { SessionConfig copy = new SessionConfig(); copy.sessionId = this.sessionId; From 063be9470dce41d41c64f8ad9f9e1869ec9e8e34 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 17 Feb 2026 09:50:56 -0500 Subject: [PATCH 11/12] Potential fix for pull request finding 'Missing Override annotation' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- .../java/com/github/copilot/sdk/json/CopilotClientOptions.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java b/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java index 89b196dd8..28a75bc6f 100644 --- a/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java +++ b/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java @@ -335,7 +335,6 @@ public CopilotClientOptions setUseLoggedInUser(Boolean useLoggedInUser) { * @return a clone of this options instance */ @Override - @Override public CopilotClientOptions clone() { CopilotClientOptions copy = new CopilotClientOptions(); copy.cliPath = this.cliPath; From 6e2e9919387a3a10f2c32defe50d84746bfe73cf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 14:56:53 +0000 Subject: [PATCH 12/12] Fix test and clone implementation per PR feedback - Fix sessionConfigListIndependence test to properly validate list independence by mutating original list after cloning - Clone environment map in CopilotClientOptions for collection independence - Add copilotClientOptionsEnvironmentIndependence test to validate environment map cloning - Update CopilotClientOptions Javadoc to reflect environment map is now copied - Apply spotless formatting Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --- .../sdk/json/CopilotClientOptions.java | 8 +-- .../copilot/sdk/json/ResumeSessionConfig.java | 4 +- .../copilot/sdk/json/SessionConfig.java | 4 +- .../github/copilot/sdk/ConfigCloneTest.java | 59 ++++++++++++------- 4 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java b/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java index 28a75bc6f..d83b8b6eb 100644 --- a/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java +++ b/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java @@ -328,9 +328,9 @@ public CopilotClientOptions setUseLoggedInUser(Boolean useLoggedInUser) { * Creates a shallow clone of this {@code CopilotClientOptions} instance. *

* Array properties (like {@code cliArgs}) are copied into new arrays so that - * modifications to the clone do not affect the original. Other reference-type - * properties (like {@code environment}) are shared between the original and - * clone. + * modifications to the clone do not affect the original. The + * {@code environment} map is also copied to a new map instance. Other + * reference-type properties are shared between the original and clone. * * @return a clone of this options instance */ @@ -346,7 +346,7 @@ public CopilotClientOptions clone() { copy.logLevel = this.logLevel; copy.autoStart = this.autoStart; copy.autoRestart = this.autoRestart; - copy.environment = this.environment; + copy.environment = this.environment != null ? new java.util.HashMap<>(this.environment) : null; copy.githubToken = this.githubToken; copy.useLoggedInUser = this.useLoggedInUser; return copy; diff --git a/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java b/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java index 182467c23..fc790258a 100644 --- a/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java +++ b/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java @@ -483,8 +483,8 @@ public ResumeSessionConfig setInfiniteSessions(InfiniteSessionConfig infiniteSes * Mutable collection properties are copied into new collection instances so * that modifications to those collections on the clone do not affect the * original. Other reference-type properties (like provider configuration, - * system messages, hooks, infinite session configuration, and handlers) are - * not deep-cloned; the original and the clone will share those objects. + * system messages, hooks, infinite session configuration, and handlers) are not + * deep-cloned; the original and the clone will share those objects. * * @return a clone of this config instance */ diff --git a/src/main/java/com/github/copilot/sdk/json/SessionConfig.java b/src/main/java/com/github/copilot/sdk/json/SessionConfig.java index 6b29c5bb4..064fee9f3 100644 --- a/src/main/java/com/github/copilot/sdk/json/SessionConfig.java +++ b/src/main/java/com/github/copilot/sdk/json/SessionConfig.java @@ -520,8 +520,8 @@ public SessionConfig setConfigDir(String configDir) { * Mutable collection properties are copied into new collection instances so * that modifications to those collections on the clone do not affect the * original. Other reference-type properties (like provider configuration, - * system messages, hooks, infinite session configuration, and handlers) are - * not deep-cloned; the original and the clone will share those objects. + * system messages, hooks, infinite session configuration, and handlers) are not + * deep-cloned; the original and the clone will share those objects. * * @return a clone of this config instance */ diff --git a/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java b/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java index 6c834edbb..3bd1b2344 100644 --- a/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java +++ b/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java @@ -7,7 +7,9 @@ import static org.junit.jupiter.api.Assertions.*; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Test; @@ -24,9 +26,9 @@ void copilotClientOptionsCloneBasic() { original.setCliPath("/usr/local/bin/copilot"); original.setLogLevel("debug"); original.setPort(9000); - + CopilotClientOptions cloned = original.clone(); - + assertEquals(original.getCliPath(), cloned.getCliPath()); assertEquals(original.getLogLevel(), cloned.getLogLevel()); assertEquals(original.getPort(), cloned.getPort()); @@ -37,23 +39,40 @@ void copilotClientOptionsArrayIndependence() { CopilotClientOptions original = new CopilotClientOptions(); String[] args = {"--flag1", "--flag2"}; original.setCliArgs(args); - + CopilotClientOptions cloned = original.clone(); cloned.getCliArgs()[0] = "--changed"; - + assertEquals("--flag1", original.getCliArgs()[0]); assertEquals("--changed", cloned.getCliArgs()[0]); } + @Test + void copilotClientOptionsEnvironmentIndependence() { + CopilotClientOptions original = new CopilotClientOptions(); + Map env = new HashMap<>(); + env.put("KEY1", "value1"); + original.setEnvironment(env); + + CopilotClientOptions cloned = original.clone(); + + // Mutate the original environment map to test independence + env.put("KEY2", "value2"); + + // The cloned config should be unaffected by mutations to the original map + assertEquals(1, cloned.getEnvironment().size()); + assertEquals(2, original.getEnvironment().size()); + } + @Test void sessionConfigCloneBasic() { SessionConfig original = new SessionConfig(); original.setSessionId("my-session"); original.setModel("gpt-4o"); original.setStreaming(true); - + SessionConfig cloned = original.clone(); - + assertEquals(original.getSessionId(), cloned.getSessionId()); assertEquals(original.getModel(), cloned.getModel()); assertEquals(original.isStreaming(), cloned.isStreaming()); @@ -66,15 +85,15 @@ void sessionConfigListIndependence() { toolList.add("grep"); toolList.add("bash"); original.setAvailableTools(toolList); - + SessionConfig cloned = original.clone(); - - List clonedTools = new ArrayList<>(cloned.getAvailableTools()); - clonedTools.add("web"); - cloned.setAvailableTools(clonedTools); - - assertEquals(2, original.getAvailableTools().size()); - assertEquals(3, cloned.getAvailableTools().size()); + + // Mutate the original list directly to test independence + toolList.add("web"); + + // The cloned config should be unaffected by mutations to the original list + assertEquals(2, cloned.getAvailableTools().size()); + assertEquals(3, original.getAvailableTools().size()); } @Test @@ -82,9 +101,9 @@ void resumeSessionConfigCloneBasic() { ResumeSessionConfig original = new ResumeSessionConfig(); original.setModel("o1"); original.setStreaming(false); - + ResumeSessionConfig cloned = original.clone(); - + assertEquals(original.getModel(), cloned.getModel()); assertEquals(original.isStreaming(), cloned.isStreaming()); } @@ -94,9 +113,9 @@ void messageOptionsCloneBasic() { MessageOptions original = new MessageOptions(); original.setPrompt("What is 2+2?"); original.setMode("immediate"); - + MessageOptions cloned = original.clone(); - + assertEquals(original.getPrompt(), cloned.getPrompt()); assertEquals(original.getMode(), cloned.getMode()); } @@ -106,11 +125,11 @@ void clonePreservesNullFields() { CopilotClientOptions opts = new CopilotClientOptions(); CopilotClientOptions optsClone = opts.clone(); assertNull(optsClone.getCliPath()); - + SessionConfig cfg = new SessionConfig(); SessionConfig cfgClone = cfg.clone(); assertNull(cfgClone.getModel()); - + MessageOptions msg = new MessageOptions(); MessageOptions msgClone = msg.clone(); assertNull(msgClone.getMode());