Skip to content

Commit ed1654c

Browse files
CopilotedburnsCopilot
authored
[Java] Add RuntimeConnection hierarchy and in-process transport dispatch (#2234)
* Initial plan * Add RuntimeConnection hierarchy and in-process transport to Java client Co-authored-by: edburns <75821+edburns@users.noreply.github.com> * Validate connection conflicts for env-var-resolved connections and fix Javadoc When COPILOT_SDK_DEFAULT_CONNECTION overrides transport inference (e.g. inprocess), also call validateConnectionConflicts so that stale legacy transport options are detected early. Update inferConnectionFromOptions to carry cliArgs onto the inferred connection so that validation does not incorrectly reject matching values. Fix CopilotClientOptions.setConnection() Javadoc to clarify that only conflicting legacy option values throw; matching values are accepted. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: edburns <75821+edburns@users.noreply.github.com> Co-authored-by: Ed Burns <edburns@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent db58460 commit ed1654c

11 files changed

Lines changed: 1102 additions & 11 deletions

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

Lines changed: 315 additions & 4 deletions
Large diffs are not rendered by default.

java/sdk/src/main/java/com/github/copilot/ffi/NativeRuntimeLoader.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
public final class NativeRuntimeLoader {
3838

3939
static final String RUNTIME_FILENAME = "runtime.node";
40-
static final String COPILOT_CLI_PATH_ENV = "COPILOT_CLI_PATH";
40+
/** Environment variable that overrides where the runtime is loaded from. */
41+
public static final String COPILOT_CLI_PATH_ENV = "COPILOT_CLI_PATH";
4142
static final String VERSION_RESOURCE = "copilot-runtime.properties";
4243

4344
/**
@@ -113,7 +114,7 @@ public static Path resolve() throws IOException {
113114
String classifier = PlatformDetector.detectClassifier();
114115
String version = readVersion(loader);
115116
Path cacheBase = defaultCacheBase();
116-
return resolve(null, findCliOnPath(), cacheBase, loader, classifier, version);
117+
return resolve(null, findRuntimeOnPath(), cacheBase, loader, classifier, version);
117118
}
118119

119120
/**
@@ -314,7 +315,12 @@ private static void copyResourceToTemp(URL resource, String resourcePath, Path t
314315
}
315316
}
316317

317-
private static String findCliOnPath() {
318+
/**
319+
* Finds the runtime executable on the {@code PATH}.
320+
*
321+
* @return the absolute path, or {@code null} if none was found
322+
*/
323+
public static String findRuntimeOnPath() {
318324
String pathValue = System.getenv("PATH");
319325
if (pathValue == null || pathValue.isBlank()) {
320326
return null;

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public class CopilotClientOptions {
5151
private String[] cliArgs;
5252
private String cliPath;
5353
private String cliUrl;
54+
private RuntimeConnection connection;
5455
private String copilotHome;
5556
private String cwd;
5657
private Map<String, String> environment;
@@ -204,6 +205,39 @@ public CopilotClientOptions setCliUrl(String cliUrl) {
204205
return this;
205206
}
206207

208+
/**
209+
* Gets the connection that selects how the client reaches the Copilot runtime.
210+
*
211+
* @return the connection, or {@code null} to infer the transport from
212+
* {@link #isUseStdio()}, {@link #getCliUrl()} and {@link #getCliPath()}
213+
*/
214+
@JsonIgnore
215+
public RuntimeConnection getConnection() {
216+
return connection;
217+
}
218+
219+
/**
220+
* Sets the connection that selects how the client reaches the Copilot runtime.
221+
* <p>
222+
* When set, the connection takes precedence over the transport-selecting
223+
* options {@link #setUseStdio(boolean)}, {@link #setCliUrl(String)},
224+
* {@link #setCliPath(String)}, {@link #setPort(int)} and
225+
* {@link #setTcpConnectionToken(String)}; combining a connection with
226+
* conflicting values for any of those options makes the client constructor
227+
* throw {@link IllegalArgumentException}. Values that match what the connection
228+
* implies are accepted, so the same options instance can be reused across
229+
* multiple client constructions.
230+
*
231+
* @param connection
232+
* the connection, or {@code null} to infer the transport from the
233+
* individual transport options
234+
* @return this options instance for method chaining
235+
*/
236+
public CopilotClientOptions setConnection(RuntimeConnection connection) {
237+
this.connection = connection;
238+
return this;
239+
}
240+
207241
/**
208242
* Gets the base directory for Copilot data (session state, config, etc.).
209243
*
@@ -754,6 +788,7 @@ public CopilotClientOptions clone() {
754788
copy.cliArgs = this.cliArgs != null ? this.cliArgs.clone() : null;
755789
copy.cliPath = this.cliPath;
756790
copy.cliUrl = this.cliUrl;
791+
copy.connection = this.connection;
757792
copy.copilotHome = this.copilotHome;
758793
copy.cwd = this.cwd;
759794
copy.environment = this.environment != null ? new java.util.HashMap<>(this.environment) : null;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
package com.github.copilot.rpc;
6+
7+
import com.github.copilot.CopilotExperimental;
8+
9+
/**
10+
* Hosts the runtime in-process by loading its native library and communicating
11+
* over the C ABI — no child process is spawned by the SDK for JSON-RPC
12+
* transport. Construct with {@link RuntimeConnection#forInProcess()}.
13+
* <p>
14+
* The in-process runtime is self-contained: it carries everything it needs and
15+
* requires no external installation. Because it runs inside the host process,
16+
* per-client process settings ({@code environment}, {@code telemetry},
17+
* {@code cwd}, and {@code cliArgs}) are rejected; configure those on the host
18+
* process instead, or use a child-process connection.
19+
*
20+
* @since 1.0.0
21+
*/
22+
@CopilotExperimental
23+
public final class InProcessRuntimeConnection extends RuntimeConnection {
24+
25+
InProcessRuntimeConnection() {
26+
}
27+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
package com.github.copilot.rpc;
6+
7+
import com.github.copilot.CopilotExperimental;
8+
9+
/**
10+
* Configures how a {@link com.github.copilot.CopilotClient} connects to the
11+
* Copilot runtime.
12+
* <p>
13+
* Instances are created through the factory methods on this class and assigned
14+
* with {@link CopilotClientOptions#setConnection(RuntimeConnection)}:
15+
*
16+
* <pre>{@code
17+
* // Spawn a runtime child process and talk over stdin/stdout (the default).
18+
* new CopilotClientOptions().setConnection(RuntimeConnection.forStdio());
19+
*
20+
* // Spawn a runtime child process listening on a TCP socket.
21+
* new CopilotClientOptions().setConnection(RuntimeConnection.forTcp().setPath("/usr/local/bin/copilot"));
22+
*
23+
* // Connect to an already-running runtime.
24+
* new CopilotClientOptions().setConnection(RuntimeConnection.forUri("localhost:3000"));
25+
* }</pre>
26+
*
27+
* @since 1.0.0
28+
*/
29+
public abstract sealed class RuntimeConnection
30+
permits StdioRuntimeConnection, TcpRuntimeConnection, UriRuntimeConnection, InProcessRuntimeConnection {
31+
32+
RuntimeConnection() {
33+
}
34+
35+
/**
36+
* Spawns a runtime child process and communicates over its stdin/stdout. This
37+
* is the default when no connection is configured.
38+
*
39+
* @return a new stdio connection
40+
*/
41+
public static StdioRuntimeConnection forStdio() {
42+
return new StdioRuntimeConnection();
43+
}
44+
45+
/**
46+
* Spawns a runtime child process at the given path and communicates over its
47+
* stdin/stdout.
48+
*
49+
* @param path
50+
* path to the runtime executable, or {@code null} to use the runtime
51+
* discovered on the {@code PATH}
52+
* @return a new stdio connection
53+
*/
54+
public static StdioRuntimeConnection forStdio(String path) {
55+
return new StdioRuntimeConnection().setPath(path);
56+
}
57+
58+
/**
59+
* Spawns a runtime child process that listens on a TCP socket and connects to
60+
* it.
61+
*
62+
* @return a new TCP connection
63+
*/
64+
public static TcpRuntimeConnection forTcp() {
65+
return new TcpRuntimeConnection();
66+
}
67+
68+
/**
69+
* Connects to an already-running runtime at the given URL.
70+
*
71+
* @param url
72+
* URL of the runtime to connect to; accepts {@code "port"},
73+
* {@code "host:port"}, or a full URL
74+
* @return a new URI connection
75+
* @throws IllegalArgumentException
76+
* if {@code url} is {@code null} or empty
77+
*/
78+
public static UriRuntimeConnection forUri(String url) {
79+
return new UriRuntimeConnection(url);
80+
}
81+
82+
/**
83+
* Hosts the runtime in-process by loading its native library and communicating
84+
* over the C ABI — no child process is spawned by the SDK for JSON-RPC
85+
* transport.
86+
*
87+
* @return a new in-process connection
88+
*/
89+
@CopilotExperimental
90+
public static InProcessRuntimeConnection forInProcess() {
91+
return new InProcessRuntimeConnection();
92+
}
93+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
package com.github.copilot.rpc;
6+
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
10+
/**
11+
* Spawns a runtime child process and communicates over its stdin/stdout.
12+
* Construct with {@link RuntimeConnection#forStdio()} or
13+
* {@link RuntimeConnection#forStdio(String)}.
14+
*
15+
* @since 1.0.0
16+
*/
17+
public final class StdioRuntimeConnection extends RuntimeConnection {
18+
19+
private String path;
20+
private List<String> args;
21+
22+
StdioRuntimeConnection() {
23+
}
24+
25+
/**
26+
* Returns the path to the runtime executable.
27+
*
28+
* @return the path, or {@code null} to use the runtime discovered on the
29+
* {@code PATH}
30+
*/
31+
public String getPath() {
32+
return path;
33+
}
34+
35+
/**
36+
* Sets the path to the runtime executable.
37+
*
38+
* @param path
39+
* the path, or {@code null} to use the runtime discovered on the
40+
* {@code PATH}
41+
* @return this instance for method chaining
42+
*/
43+
public StdioRuntimeConnection setPath(String path) {
44+
this.path = path;
45+
return this;
46+
}
47+
48+
/**
49+
* Returns the extra command-line arguments passed to the runtime process.
50+
*
51+
* @return the arguments, or {@code null} if none are configured
52+
*/
53+
public List<String> getArgs() {
54+
return args;
55+
}
56+
57+
/**
58+
* Sets extra command-line arguments passed to the runtime process.
59+
*
60+
* @param args
61+
* the arguments, or {@code null} for none
62+
* @return this instance for method chaining
63+
*/
64+
public StdioRuntimeConnection setArgs(List<String> args) {
65+
this.args = args == null ? null : new ArrayList<>(args);
66+
return this;
67+
}
68+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
package com.github.copilot.rpc;
6+
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
10+
/**
11+
* Spawns a runtime child process listening on a TCP socket and connects to it.
12+
* Construct with {@link RuntimeConnection#forTcp()}.
13+
*
14+
* @since 1.0.0
15+
*/
16+
public final class TcpRuntimeConnection extends RuntimeConnection {
17+
18+
private String path;
19+
private int port;
20+
private String connectionToken;
21+
private List<String> args;
22+
23+
TcpRuntimeConnection() {
24+
}
25+
26+
/**
27+
* Returns the path to the runtime executable.
28+
*
29+
* @return the path, or {@code null} to use the runtime discovered on the
30+
* {@code PATH}
31+
*/
32+
public String getPath() {
33+
return path;
34+
}
35+
36+
/**
37+
* Sets the path to the runtime executable.
38+
*
39+
* @param path
40+
* the path, or {@code null} to use the runtime discovered on the
41+
* {@code PATH}
42+
* @return this instance for method chaining
43+
*/
44+
public TcpRuntimeConnection setPath(String path) {
45+
this.path = path;
46+
return this;
47+
}
48+
49+
/**
50+
* Returns the TCP port the spawned runtime listens on.
51+
*
52+
* @return the port, or {@code 0} to auto-allocate a free port
53+
*/
54+
public int getPort() {
55+
return port;
56+
}
57+
58+
/**
59+
* Sets the TCP port the spawned runtime listens on.
60+
*
61+
* @param port
62+
* the port, or {@code 0} (the default) to auto-allocate a free port
63+
* @return this instance for method chaining
64+
*/
65+
public TcpRuntimeConnection setPort(int port) {
66+
this.port = port;
67+
return this;
68+
}
69+
70+
/**
71+
* Returns the shared secret the SDK sends to the spawned runtime to
72+
* authenticate the TCP connection.
73+
*
74+
* @return the token, or {@code null} to generate one automatically
75+
*/
76+
public String getConnectionToken() {
77+
return connectionToken;
78+
}
79+
80+
/**
81+
* Sets the shared secret the SDK sends to the spawned runtime to authenticate
82+
* the TCP connection.
83+
*
84+
* @param connectionToken
85+
* the token, or {@code null} to generate one automatically
86+
* @return this instance for method chaining
87+
*/
88+
public TcpRuntimeConnection setConnectionToken(String connectionToken) {
89+
this.connectionToken = connectionToken;
90+
return this;
91+
}
92+
93+
/**
94+
* Returns the extra command-line arguments passed to the runtime process.
95+
*
96+
* @return the arguments, or {@code null} if none are configured
97+
*/
98+
public List<String> getArgs() {
99+
return args;
100+
}
101+
102+
/**
103+
* Sets extra command-line arguments passed to the runtime process.
104+
*
105+
* @param args
106+
* the arguments, or {@code null} for none
107+
* @return this instance for method chaining
108+
*/
109+
public TcpRuntimeConnection setArgs(List<String> args) {
110+
this.args = args == null ? null : new ArrayList<>(args);
111+
return this;
112+
}
113+
}

0 commit comments

Comments
 (0)