A Java SDK for programmatic control of GitHub Copilot CLI. This is a port of the official .NET SDK, targeting Java 17+.
These instructions guide GitHub Copilot when assisting with this repository. They cover:
- Tech Stack: Java 17+, Maven, Jackson for JSON, JUnit for testing
- Purpose: Provide a Java SDK for programmatic control of GitHub Copilot CLI
- Architecture: JSON-RPC client communicating with Copilot CLI over stdio
- Key Goals: Maintain parity with upstream .NET SDK while following Java idioms
# Build and run all tests
mvn clean verify
# Run a single test class
mvn test -Dtest=CopilotClientTest
# Run a single test method
mvn test -Dtest=ToolsTest#testToolInvocation
# Format code (required before commit)
mvn spotless:apply
# Check formatting only
mvn spotless:check
# Build without tests
mvn clean package -DskipTests
# Run tests with debug logging
mvn test -PdebugWhen running tests to verify changes, always use mvn verify without -q and without piping through grep. The full output is needed to diagnose failures. Do NOT use commands like:
# BAD - hides critical failure details, often requires a second run
mvn verify -q 2>&1 | grep -E 'Tests run:|BUILD'
mvn verify 2>&1 | grep -E 'Tests run.*in com|BUILD|test failure'Instead, use one of these approaches:
# GOOD - run tests with full output (preferred for investigating failures)
mvn verify
# GOOD - run tests showing just the summary and result using Maven's built-in log level
mvn verify -B --fail-at-end 2>&1 | tail -30
# GOOD - run a single test class when debugging a specific test
mvn test -Dtest=CopilotClientTestInterpreting results:
BUILD SUCCESSat the end means all tests passed. No further investigation needed.BUILD FAILUREmeans something failed. The lines immediately aboveBUILD FAILUREwill contain the relevant error information. Look for[ERROR]lines near the bottom of the output.- The Surefire summary line
Tests run: X, Failures: Y, Errors: Z, Skipped: Wappears near the end and gives the counts. - Do NOT run tests a second time just to get different output formatting. One run with full output is sufficient.
- CopilotClient - Main entry point. Manages connection to Copilot CLI server via JSON-RPC over stdio. Spawns CLI process or connects to existing server.
- CopilotSession - Represents a conversation session. Handles event subscriptions, tool registration, permissions, and message sending.
- JsonRpcClient - Low-level JSON-RPC protocol implementation using Jackson for serialization.
com.github.copilot.sdk- Core classes (CopilotClient, CopilotSession, JsonRpcClient)com.github.copilot.sdk.json- DTOs, request/response types, handler interfaces (SessionConfig, MessageOptions, ToolDefinition, etc.)com.github.copilot.sdk.events- Event types for session streaming (AssistantMessageEvent, SessionIdleEvent, ToolExecutionStartEvent, etc.)
Tests use the official copilot-sdk test harness from https://github.com/github/copilot-sdk. The harness is automatically cloned during generate-test-resources phase to target/copilot-sdk/.
- E2ETestContext - Manages test environment with CapiProxy for deterministic API responses
- CapiProxy - Node.js-based replaying proxy using YAML snapshots from
test/snapshots/ - Test snapshots are stored in the upstream repo's
test/snapshots/directory
This SDK tracks the official .NET implementation at github/copilot-sdk. The .lastmerge file contains the last merged upstream commit hash. Use the agentic-merge-upstream skill (see .github/prompts/agentic-merge-upstream.prompt.md) to port changes.
When porting from .NET:
- Adapt to Java idioms, don't copy C# patterns directly
- Convert
async/await→CompletableFuture - Convert C# properties → Java getters/setters or fluent setters
- Use Jackson for JSON (
ObjectMapper,@JsonProperty)
- 4-space indentation (enforced by Spotless with Eclipse formatter)
- Fluent setter pattern for configuration classes (e.g.,
new SessionConfig().setModel("gpt-5").setTools(tools)) - Public APIs require Javadoc (enforced by Checkstyle, except
jsonandeventspackages) - Pre-commit hook runs
mvn spotless:check- enable with:git config core.hooksPath .githooks
Handlers use functional interfaces with CompletableFuture returns:
session.createSession(new SessionConfig()
.setOnPermissionRequest((request, invocation) ->
CompletableFuture.completedFuture(new PermissionRequestResult().setKind("allow")))
.setOnUserInput((request, invocation) ->
CompletableFuture.completedFuture(new UserInputResponse().setResponse("user input")))
);Sessions emit typed events via session.on():
session.on(AssistantMessageEvent.class, msg -> System.out.println(msg.getData().content()));
session.on(SessionIdleEvent.class, idle -> done.complete(null));AbstractSessionEvent is a sealed class permitting specific event types. Use pattern matching:
switch (event) {
case AssistantMessageEvent msg -> handleMessage(msg);
case ToolExecutionStartEvent tool -> handleToolStart(tool);
case SessionIdleEvent idle -> handleIdle();
default -> { }
}Custom tools use ToolDefinition.create() with JSON Schema parameters and a ToolHandler:
var tool = ToolDefinition.create(
"get_weather",
"Get weather for a location",
Map.of(
"type", "object",
"properties", Map.of("location", Map.of("type", "string")),
"required", List.of("location")
),
invocation -> {
// Type-safe: invocation.getArgumentsAs(WeatherArgs.class)
// Or Map-based: invocation.getArguments().get("location")
return CompletableFuture.completedFuture(result);
}
);Tests extend the shared context pattern:
private static E2ETestContext ctx;
@BeforeAll
static void setup() throws Exception {
ctx = E2ETestContext.create();
}
@AfterAll
static void teardown() throws Exception {
if (ctx != null) ctx.close();
}
@Test
void testFeature() throws Exception {
ctx.configureForTest("category", "test_name"); // Loads test/snapshots/category/test_name.yaml
try (CopilotClient client = ctx.createClient()) {
// Test logic
}
}Test method names are converted to lowercase snake_case for snapshot filenames to avoid case collisions on macOS/Windows.
- Uses Jackson with
@JsonPropertyannotations @JsonInclude(JsonInclude.Include.NON_NULL)on DTOs to omit null fieldsObjectMapperconfigured viaJsonRpcClient.getObjectMapper()with:JavaTimeModulefor date/time handlingFAIL_ON_UNKNOWN_PROPERTIES = falsefor forward compatibility
- Site docs in
src/site/markdown/(filtered for${project.version}substitution) - Update
src/site/site.xmlwhen adding new documentation pages - Javadoc required for public APIs except
jsonandeventspackages (self-documenting DTOs) - Copilot CLI Version: When updating the required Copilot CLI version in
README.md, also update it insrc/site/markdown/index.mdto keep them in sync
- DO NOT edit
.github/agents/directory - these contain instructions for other agents - DO NOT modify
target/directory - this contains build artifacts - DO NOT edit
pom.xmldependencies without careful consideration - this SDK has minimal dependencies by design - DO NOT change the Jackson version without testing against all serialization patterns
- DO NOT modify test snapshots in
target/copilot-sdk/test/snapshots/- these come from upstream - DO NOT alter the Eclipse formatter configuration in
pom.xmlwithout team consensus - DO NOT remove or skip Checkstyle or Spotless checks
- NEVER commit secrets, API keys, tokens, or credentials to the repository
- NEVER commit
.envfiles or any files containing sensitive configuration - NEVER log sensitive data in code (API keys, tokens, user data)
- Always use
try-with-resourcesfor streams and readers to prevent resource leaks - Always use
StandardCharsets.UTF_8when creating InputStreamReader/OutputStreamWriter - Review any new dependencies for known security vulnerabilities before adding them
- When handling user input in tools or handlers, consider injection risks
This SDK is designed to be lightweight with minimal dependencies:
- Core dependencies: Jackson (JSON), JUnit (tests only)
- Before adding new dependencies:
- Consider if the functionality can be implemented without a new dependency
- Check if Jackson already provides the needed functionality
- Ensure the dependency is actively maintained and widely used
- Verify compatibility with Java 17+
- Check for security vulnerabilities
- Get team approval for non-trivial additions
- Use clear, descriptive commit messages
- Start with a verb in present tense (e.g., "Add", "Fix", "Update", "Refactor")
- Keep the first line under 72 characters
- Add details in the body if needed
- Examples:
Add support for streaming responsesFix resource leak in JsonRpcClientUpdate documentation for tool handlersRefactor event handling to use sealed classes
- Keep PRs focused and minimal - one feature/fix per PR
- Ensure all tests pass before requesting review
- Run
mvn spotless:applybefore committing - Include tests for new functionality
- Update documentation if adding/changing public APIs
- Reference related issues using
#issue-number - For upstream merges, follow the
agentic-merge-upstreamskill workflow
- Setup: Enable git hooks with
git config core.hooksPath .githooks - Branch: Create feature branches from
main - Code: Write code following the conventions above
- Format: Run
mvn spotless:applyto format code - Test: Run
mvn clean verifyto ensure all tests pass - Commit: Make focused commits with clear messages
- Push: Push your branch and create a PR
- Review: Address review feedback and iterate
The release process is automated via the publish-maven.yml GitHub Actions workflow. Key steps:
-
CHANGELOG Update: The script
.github/scripts/release/update-changelog.shautomatically:- Converts the
## [Unreleased]section to## [version] - date - Creates a new empty
## [Unreleased]section at the top - Updates version comparison links at the bottom of CHANGELOG.md
- Injects the upstream SDK commit hash (from
.lastmerge) as a> **Upstream sync:**blockquote in both the new[Unreleased]section and the released version section
- Converts the
-
Upstream Sync Tracking: Each release records which commit from the official
github/copilot-sdkit is synced to:- The
.lastmergefile is read during the release workflow - The commit hash is injected into
CHANGELOG.mdunder the release heading - Format:
> **Upstream sync:** [\github/copilot-sdk@SHORT_HASH`](link-to-commit)`
- The
-
Documentation Updates: README.md and jbang-example.java are updated with the new version.
-
Maven Release: Uses
maven-release-pluginto:- Update pom.xml version
- Create a git tag
- Deploy to Maven Central
-
Rollback: If the release fails, the documentation commit is automatically reverted
The workflow is triggered manually via workflow_dispatch with optional version parameters.