Java SDK for programmatic control of GitHub Copilot CLI.
Note: This SDK is in technical preview and may change in breaking ways.
- Java 21 or later
- GitHub Copilot CLI installed and in PATH (or provide custom
cliPath)
Run mvn install locally, then configure the dependency in your project.
<dependency>
<groupId>com.github.copilot</groupId>
<artifactId>copilot-sdk</artifactId>
<version>0.1.0</version>
</dependency>implementation 'com.github.copilot:copilot-sdk:0.1.0'import com.github.copilot.sdk.*;
import com.github.copilot.sdk.events.*;
import com.github.copilot.sdk.json.*;
import java.util.concurrent.CompletableFuture;
public class Example {
public static void main(String[] args) throws Exception {
// Create and start client
try (var client = new CopilotClient()) {
client.start().get();
// Create a session
var session = client.createSession(
new SessionConfig().setModel(CopilotModel.CLAUDE_SONNET_4_5.toString())
).get();
// Wait for response using session.idle event
var done = new CompletableFuture<Void>();
session.on(evt -> {
if (evt instanceof AssistantMessageEvent msg) {
System.out.println(msg.getData().getContent());
} else if (evt instanceof SessionIdleEvent) {
done.complete(null);
}
});
// Send a message and wait for completion
session.send(new MessageOptions().setPrompt("What is 2+2?")).get();
done.get();
}
}
}You can quickly try the SDK without setting up a full project using JBang:
# Assuming you are in the `java/` directory of this repository
# Install the SDK locally first (not yet on Maven Central)
mvn install
# Install JBang (if not already installed)
# macOS: brew install jbang
# Linux/Windows: curl -Ls https://sh.jbang.dev | bash -s - app setup
# Run the example
jbang jbang-example.javaThe jbang-example.java file includes the dependency declaration and can be run directly:
//DEPS com.github.copilot:copilot-sdk:0.1.0new CopilotClient()
new CopilotClient(CopilotClientOptions options)Options:
cliPath- Path to CLI executable (default: "copilot" from PATH)cliArgs- Extra arguments prepended before SDK-managed flagscliUrl- URL of existing CLI server to connect to (e.g.,"localhost:8080"). When provided, the client will not spawn a CLI process.port- Server port (default: 0 for random)useStdio- Use stdio transport instead of TCP (default: true)logLevel- Log level (default: "info")autoStart- Auto-start server (default: true)autoRestart- Auto-restart on crash (default: true)cwd- Working directory for the CLI processenvironment- Environment variables to pass to the CLI process
Start the CLI server and establish connection.
Stop the server and close all sessions.
Force stop the CLI server without graceful cleanup.
Create a new conversation session.
Config:
sessionId- Custom session IDmodel- Model to use ("gpt-5", "claude-sonnet-4.5", etc.)tools- Custom tools exposed to the CLIsystemMessage- System message customizationavailableTools- List of tool names to allowexcludedTools- List of tool names to disableprovider- Custom API provider configuration (BYOK)streaming- Enable streaming of response chunks (default: false)mcpServers- MCP server configurationscustomAgents- Custom agent configurationsonPermissionRequest- Handler for permission requests
Resume an existing session.
Ping the server to check connectivity.
Get current connection state. Returns one of: DISCONNECTED, CONNECTING, CONNECTED, ERROR.
List all available sessions.
Delete a session and its data from disk.
Get the ID of the most recently used session.
Represents a single conversation session.
getSessionId()- The unique identifier for this session
Send a message to the session.
Options:
prompt- The message/prompt to sendattachments- File attachmentsmode- Delivery mode ("enqueue" or "immediate")
Returns the message ID.
Send a message and wait for the session to become idle. Default timeout is 60 seconds.
Subscribe to session events. Returns a Closeable to unsubscribe.
var subscription = session.on(evt -> {
System.out.println("Event: " + evt.getType());
});
// Later...
subscription.close();Abort the currently processing message in this session.
Get all events/messages from this session.
Dispose the session and free resources.
Sessions emit various events during processing. Each event type extends AbstractSessionEvent:
UserMessageEvent- User message addedAssistantMessageEvent- Assistant responseAssistantMessageDeltaEvent- Streaming response chunkToolExecutionStartEvent- Tool execution startedToolExecutionCompleteEvent- Tool execution completedSessionStartEvent- Session startedSessionIdleEvent- Session is idleSessionErrorEvent- Session error occurredSessionResumeEvent- Session was resumed- And more...
Use pattern matching (Java 21+) to handle specific event types:
session.on(evt -> {
if (evt instanceof AssistantMessageEvent msg) {
System.out.println(msg.getData().getContent());
} else if (evt instanceof SessionErrorEvent err) {
System.out.println("Error: " + err.getData().getMessage());
}
});Enable streaming to receive assistant response chunks as they're generated:
var session = client.createSession(
new SessionConfig()
.setModel("gpt-5")
.setStreaming(true)
).get();
var done = new CompletableFuture<Void>();
session.on(evt -> {
if (evt instanceof AssistantMessageDeltaEvent delta) {
// Streaming message chunk - print incrementally
System.out.print(delta.getData().getDeltaContent());
} else if (evt instanceof AssistantMessageEvent msg) {
// Final message - complete content
System.out.println("\n--- Final message ---");
System.out.println(msg.getData().getContent());
} else if (evt instanceof SessionIdleEvent) {
done.complete(null);
}
});
session.send(new MessageOptions().setPrompt("Tell me a short story")).get();
done.get();var client = new CopilotClient(
new CopilotClientOptions().setAutoStart(false)
);
// Start manually
client.start().get();
// Use client...
// Stop manually
client.stop().get();You can let the CLI call back into your process when the model needs capabilities you own:
var lookupTool = ToolDefinition.create(
"lookup_issue",
"Fetch issue details from our tracker",
Map.of(
"type", "object",
"properties", Map.of(
"id", Map.of("type", "string", "description", "Issue identifier")
),
"required", List.of("id")
),
invocation -> {
String id = ((Map<String, Object>) invocation.getArguments()).get("id").toString();
return CompletableFuture.completedFuture(fetchIssue(id));
}
);
var session = client.createSession(
new SessionConfig()
.setModel("gpt-5")
.setTools(List.of(lookupTool))
).get();Control the system prompt using SystemMessageConfig in session config:
var session = client.createSession(
new SessionConfig()
.setModel("gpt-5")
.setSystemMessage(new SystemMessageConfig()
.setMode(SystemMessageMode.APPEND)
.setContent("""
<workflow_rules>
- Always check for security vulnerabilities
- Suggest performance improvements when applicable
</workflow_rules>
"""))
).get();For full control (removes all guardrails), use REPLACE mode:
var session = client.createSession(
new SessionConfig()
.setModel("gpt-5")
.setSystemMessage(new SystemMessageConfig()
.setMode(SystemMessageMode.REPLACE)
.setContent("You are a helpful assistant."))
).get();var session1 = client.createSession(
new SessionConfig().setModel("gpt-5")
).get();
var session2 = client.createSession(
new SessionConfig().setModel("claude-sonnet-4.5")
).get();
// Both sessions are independent
session1.send(new MessageOptions().setPrompt("Hello from session 1")).get();
session2.send(new MessageOptions().setPrompt("Hello from session 2")).get();session.send(new MessageOptions()
.setPrompt("Analyze this file")
.setAttachments(List.of(
new Attachment()
.setType("file")
.setPath("/path/to/file.java")
.setDisplayName("My File")
))
).get();Use a custom API provider:
var session = client.createSession(
new SessionConfig()
.setProvider(new ProviderConfig()
.setType("openai")
.setBaseUrl("https://api.openai.com/v1")
.setApiKey("your-api-key"))
).get();Handle permission requests from the CLI:
var session = client.createSession(
new SessionConfig()
.setModel("gpt-5")
.setOnPermissionRequest((request, invocation) -> {
// Approve or deny the permission request
var result = new PermissionRequestResult();
result.setKind("user-approved");
return CompletableFuture.completedFuture(result);
})
).get();try {
var session = client.createSession().get();
session.send(new MessageOptions().setPrompt("Hello")).get();
} catch (ExecutionException ex) {
Throwable cause = ex.getCause();
System.err.println("Error: " + cause.getMessage());
}MIT