Skip to content

Commit 21f9560

Browse files
Copilotbrunoborges
andcommitted
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>
1 parent 1af066a commit 21f9560

File tree

6 files changed

+243
-0
lines changed

6 files changed

+243
-0
lines changed

src/main/java/com/github/copilot/sdk/CliServerManager.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ ProcessInfo startCliServer() throws IOException, InterruptedException {
8585
var pb = new ProcessBuilder(command);
8686
pb.redirectErrorStream(false);
8787

88+
// Note: On Windows, console window visibility depends on how the parent Java
89+
// process was launched. GUI applications started with 'javaw' will not create
90+
// visible console windows for subprocesses. Console applications started with
91+
// 'java' will share their console with subprocesses. Java's ProcessBuilder
92+
// doesn't provide explicit CREATE_NO_WINDOW flags like native Windows APIs,
93+
// but the default behavior is appropriate for most use cases.
94+
8895
if (options.getCwd() != null) {
8996
pb.directory(new File(options.getCwd()));
9097
}

src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,4 +323,31 @@ public CopilotClientOptions setUseLoggedInUser(Boolean useLoggedInUser) {
323323
this.useLoggedInUser = useLoggedInUser;
324324
return this;
325325
}
326+
327+
/**
328+
* Creates a shallow clone of this {@code CopilotClientOptions} instance.
329+
* <p>
330+
* Array properties (like {@code cliArgs}) are copied into new arrays so that
331+
* modifications to the clone do not affect the original. Other reference-type
332+
* properties (like {@code environment}) are shared between the original and
333+
* clone.
334+
*
335+
* @return a clone of this options instance
336+
*/
337+
public CopilotClientOptions clone() {
338+
CopilotClientOptions copy = new CopilotClientOptions();
339+
copy.cliPath = this.cliPath;
340+
copy.cliArgs = this.cliArgs != null ? this.cliArgs.clone() : null;
341+
copy.cwd = this.cwd;
342+
copy.port = this.port;
343+
copy.useStdio = this.useStdio;
344+
copy.cliUrl = this.cliUrl;
345+
copy.logLevel = this.logLevel;
346+
copy.autoStart = this.autoStart;
347+
copy.autoRestart = this.autoRestart;
348+
copy.environment = this.environment;
349+
copy.githubToken = this.githubToken;
350+
copy.useLoggedInUser = this.useLoggedInUser;
351+
return copy;
352+
}
326353
}

src/main/java/com/github/copilot/sdk/json/MessageOptions.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package com.github.copilot.sdk.json;
66

7+
import java.util.ArrayList;
78
import java.util.Collections;
89
import java.util.List;
910

@@ -107,4 +108,22 @@ public String getMode() {
107108
return mode;
108109
}
109110

111+
/**
112+
* Creates a shallow clone of this {@code MessageOptions} instance.
113+
* <p>
114+
* Mutable collection properties are copied into new collection instances so
115+
* that modifications to those collections on the clone do not affect the
116+
* original. Other reference-type properties (like attachment items) are not
117+
* deep-cloned; the original and the clone will share those objects.
118+
*
119+
* @return a clone of this options instance
120+
*/
121+
public MessageOptions clone() {
122+
MessageOptions copy = new MessageOptions();
123+
copy.prompt = this.prompt;
124+
copy.attachments = this.attachments != null ? new ArrayList<>(this.attachments) : null;
125+
copy.mode = this.mode;
126+
return copy;
127+
}
128+
110129
}

src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package com.github.copilot.sdk.json;
66

7+
import java.util.ArrayList;
78
import java.util.Collections;
89
import java.util.List;
910
import java.util.Map;
@@ -475,4 +476,39 @@ public ResumeSessionConfig setInfiniteSessions(InfiniteSessionConfig infiniteSes
475476
this.infiniteSessions = infiniteSessions;
476477
return this;
477478
}
479+
480+
/**
481+
* Creates a shallow clone of this {@code ResumeSessionConfig} instance.
482+
* <p>
483+
* Mutable collection properties are copied into new collection instances so
484+
* that modifications to those collections on the clone do not affect the
485+
* original. Other reference-type properties (like provider configuration,
486+
* system messages, hooks, infinite session configuration, and delegates) are
487+
* not deep-cloned; the original and the clone will share those objects.
488+
*
489+
* @return a clone of this config instance
490+
*/
491+
public ResumeSessionConfig clone() {
492+
ResumeSessionConfig copy = new ResumeSessionConfig();
493+
copy.model = this.model;
494+
copy.tools = this.tools != null ? new ArrayList<>(this.tools) : null;
495+
copy.systemMessage = this.systemMessage;
496+
copy.availableTools = this.availableTools != null ? new ArrayList<>(this.availableTools) : null;
497+
copy.excludedTools = this.excludedTools != null ? new ArrayList<>(this.excludedTools) : null;
498+
copy.provider = this.provider;
499+
copy.reasoningEffort = this.reasoningEffort;
500+
copy.onPermissionRequest = this.onPermissionRequest;
501+
copy.onUserInputRequest = this.onUserInputRequest;
502+
copy.hooks = this.hooks;
503+
copy.workingDirectory = this.workingDirectory;
504+
copy.configDir = this.configDir;
505+
copy.disableResume = this.disableResume;
506+
copy.streaming = this.streaming;
507+
copy.mcpServers = this.mcpServers != null ? new java.util.HashMap<>(this.mcpServers) : null;
508+
copy.customAgents = this.customAgents != null ? new ArrayList<>(this.customAgents) : null;
509+
copy.skillDirectories = this.skillDirectories != null ? new ArrayList<>(this.skillDirectories) : null;
510+
copy.disabledSkills = this.disabledSkills != null ? new ArrayList<>(this.disabledSkills) : null;
511+
copy.infiniteSessions = this.infiniteSessions;
512+
return copy;
513+
}
478514
}

src/main/java/com/github/copilot/sdk/json/SessionConfig.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package com.github.copilot.sdk.json;
66

7+
import java.util.ArrayList;
78
import java.util.Collections;
89
import java.util.List;
910
import java.util.Map;
@@ -512,4 +513,39 @@ public SessionConfig setConfigDir(String configDir) {
512513
this.configDir = configDir;
513514
return this;
514515
}
516+
517+
/**
518+
* Creates a shallow clone of this {@code SessionConfig} instance.
519+
* <p>
520+
* Mutable collection properties are copied into new collection instances so
521+
* that modifications to those collections on the clone do not affect the
522+
* original. Other reference-type properties (like provider configuration,
523+
* system messages, hooks, infinite session configuration, and delegates) are
524+
* not deep-cloned; the original and the clone will share those objects.
525+
*
526+
* @return a clone of this config instance
527+
*/
528+
public SessionConfig clone() {
529+
SessionConfig copy = new SessionConfig();
530+
copy.sessionId = this.sessionId;
531+
copy.model = this.model;
532+
copy.reasoningEffort = this.reasoningEffort;
533+
copy.tools = this.tools != null ? new ArrayList<>(this.tools) : null;
534+
copy.systemMessage = this.systemMessage;
535+
copy.availableTools = this.availableTools != null ? new ArrayList<>(this.availableTools) : null;
536+
copy.excludedTools = this.excludedTools != null ? new ArrayList<>(this.excludedTools) : null;
537+
copy.provider = this.provider;
538+
copy.onPermissionRequest = this.onPermissionRequest;
539+
copy.onUserInputRequest = this.onUserInputRequest;
540+
copy.hooks = this.hooks;
541+
copy.workingDirectory = this.workingDirectory;
542+
copy.streaming = this.streaming;
543+
copy.mcpServers = this.mcpServers != null ? new java.util.HashMap<>(this.mcpServers) : null;
544+
copy.customAgents = this.customAgents != null ? new ArrayList<>(this.customAgents) : null;
545+
copy.infiniteSessions = this.infiniteSessions;
546+
copy.skillDirectories = this.skillDirectories != null ? new ArrayList<>(this.skillDirectories) : null;
547+
copy.disabledSkills = this.disabledSkills != null ? new ArrayList<>(this.disabledSkills) : null;
548+
copy.configDir = this.configDir;
549+
return copy;
550+
}
515551
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
package com.github.copilot.sdk;
6+
7+
import static org.junit.jupiter.api.Assertions.*;
8+
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
12+
import org.junit.jupiter.api.Test;
13+
14+
import com.github.copilot.sdk.json.CopilotClientOptions;
15+
import com.github.copilot.sdk.json.SessionConfig;
16+
import com.github.copilot.sdk.json.ResumeSessionConfig;
17+
import com.github.copilot.sdk.json.MessageOptions;
18+
19+
class ConfigCloneTest {
20+
21+
@Test
22+
void copilotClientOptionsCloneBasic() {
23+
CopilotClientOptions original = new CopilotClientOptions();
24+
original.setCliPath("/usr/local/bin/copilot");
25+
original.setLogLevel("debug");
26+
original.setPort(9000);
27+
28+
CopilotClientOptions cloned = original.clone();
29+
30+
assertEquals(original.getCliPath(), cloned.getCliPath());
31+
assertEquals(original.getLogLevel(), cloned.getLogLevel());
32+
assertEquals(original.getPort(), cloned.getPort());
33+
}
34+
35+
@Test
36+
void copilotClientOptionsArrayIndependence() {
37+
CopilotClientOptions original = new CopilotClientOptions();
38+
String[] args = {"--flag1", "--flag2"};
39+
original.setCliArgs(args);
40+
41+
CopilotClientOptions cloned = original.clone();
42+
cloned.getCliArgs()[0] = "--changed";
43+
44+
assertEquals("--flag1", original.getCliArgs()[0]);
45+
assertEquals("--changed", cloned.getCliArgs()[0]);
46+
}
47+
48+
@Test
49+
void sessionConfigCloneBasic() {
50+
SessionConfig original = new SessionConfig();
51+
original.setSessionId("my-session");
52+
original.setModel("gpt-4o");
53+
original.setStreaming(true);
54+
55+
SessionConfig cloned = original.clone();
56+
57+
assertEquals(original.getSessionId(), cloned.getSessionId());
58+
assertEquals(original.getModel(), cloned.getModel());
59+
assertEquals(original.isStreaming(), cloned.isStreaming());
60+
}
61+
62+
@Test
63+
void sessionConfigListIndependence() {
64+
SessionConfig original = new SessionConfig();
65+
List<String> toolList = new ArrayList<>();
66+
toolList.add("grep");
67+
toolList.add("bash");
68+
original.setAvailableTools(toolList);
69+
70+
SessionConfig cloned = original.clone();
71+
72+
List<String> clonedTools = new ArrayList<>(cloned.getAvailableTools());
73+
clonedTools.add("web");
74+
cloned.setAvailableTools(clonedTools);
75+
76+
assertEquals(2, original.getAvailableTools().size());
77+
assertEquals(3, cloned.getAvailableTools().size());
78+
}
79+
80+
@Test
81+
void resumeSessionConfigCloneBasic() {
82+
ResumeSessionConfig original = new ResumeSessionConfig();
83+
original.setModel("o1");
84+
original.setStreaming(false);
85+
86+
ResumeSessionConfig cloned = original.clone();
87+
88+
assertEquals(original.getModel(), cloned.getModel());
89+
assertEquals(original.isStreaming(), cloned.isStreaming());
90+
}
91+
92+
@Test
93+
void messageOptionsCloneBasic() {
94+
MessageOptions original = new MessageOptions();
95+
original.setPrompt("What is 2+2?");
96+
original.setMode("immediate");
97+
98+
MessageOptions cloned = original.clone();
99+
100+
assertEquals(original.getPrompt(), cloned.getPrompt());
101+
assertEquals(original.getMode(), cloned.getMode());
102+
}
103+
104+
@Test
105+
void clonePreservesNullFields() {
106+
CopilotClientOptions opts = new CopilotClientOptions();
107+
CopilotClientOptions optsClone = opts.clone();
108+
assertNull(optsClone.getCliPath());
109+
110+
SessionConfig cfg = new SessionConfig();
111+
SessionConfig cfgClone = cfg.clone();
112+
assertNull(cfgClone.getModel());
113+
114+
MessageOptions msg = new MessageOptions();
115+
MessageOptions msgClone = msg.clone();
116+
assertNull(msgClone.getMode());
117+
}
118+
}

0 commit comments

Comments
 (0)