diff --git a/.lastmerge b/.lastmerge index 04846a767..2e1ed67d3 100644 --- a/.lastmerge +++ b/.lastmerge @@ -1 +1 @@ -304d812cd4c98755159da427c6701bfb7e0b7c32 +5016587a62652f3d184b3c6958dfc63359921aa8 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..d83b8b6eb 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,32 @@ 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. 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 + */ + @Override + 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 != 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/MessageOptions.java b/src/main/java/com/github/copilot/sdk/json/MessageOptions.java index 99c4214b8..6ba629b7b 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,23 @@ 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 + */ + @Override + 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..fc790258a 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,40 @@ 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 handlers) are not + * deep-cloned; the original and the clone will share those objects. + * + * @return a clone of this config instance + */ + @Override + 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..064fee9f3 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,40 @@ 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 handlers) are not
+ * deep-cloned; the original and the clone will share those objects.
+ *
+ * @return a clone of this config instance
+ */
+ @Override
+ 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..3bd1b2344
--- /dev/null
+++ b/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java
@@ -0,0 +1,137 @@
+/*---------------------------------------------------------------------------------------------
+ * 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.HashMap;
+import java.util.List;
+import java.util.Map;
+
+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 copilotClientOptionsEnvironmentIndependence() {
+ CopilotClientOptions original = new CopilotClientOptions();
+ Map