diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg
index 482adb13b..f1a7c5eb3 100644
--- a/.github/badges/jacoco.svg
+++ b/.github/badges/jacoco.svg
@@ -12,7 +12,7 @@
coveragecoverage
- 84.7%
- 84.7%
+ 84.4%
+ 84.4%
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index d7dafb081..284e2b800 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -104,7 +104,7 @@ When porting from .NET:
- 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 `json` and `events` packages)
-- Pre-commit hook runs `mvn spotless:check` - enable with: `git config core.hooksPath .githooks`
+- Pre-commit hook runs `mvn spotless:check` - Must be manually enabled with: `git config core.hooksPath .githooks`, except in the Copilot coding agent environment. This hook is explicitly enabled in the Copilot coding agent environment. See [copilot-setup-steps.yml](workflows/copilot-setup-steps.yml).
### Handler Pattern
@@ -244,6 +244,18 @@ This SDK is designed to be **lightweight with minimal dependencies**:
5. Check for security vulnerabilities
6. Get team approval for non-trivial additions
+## Pre-commit Hooks and Formatting (Coding Agent)
+
+The repository has a pre-commit hook (`.githooks/pre-commit`) that is **automatically enabled** in the Copilot coding agent environment via `copilot-setup-steps.yml`. The hook runs `mvn spotless:check` on any commit that includes changes under `src/`.
+
+**If a commit fails due to the pre-commit hook:**
+
+1. Run `mvn spotless:apply` to auto-fix formatting issues.
+2. Re-stage the changed files with `git add -u`.
+3. Retry the commit.
+
+**Best practice:** Always run `mvn spotless:apply` before committing Java source changes to avoid hook failures in the first place. If you forget and the hook rejects the commit, follow the three steps above and continue.
+
## Commit and PR Guidelines
### Commit Messages
diff --git a/.github/templates/index.html b/.github/templates/index.html
index 9af01dded..d273ad074 100644
--- a/.github/templates/index.html
+++ b/.github/templates/index.html
@@ -65,8 +65,8 @@
Available Versions
-
- ⚠️ Disclaimer: This is the official Java SDK for GitHub Copilot. This repository treats the official .NET and nodejs SDKs for GitHub Copilot as reference implementations. These SDKS are all officially supported as GitHub open source projects. The Java implementation follows the backward compatibility guarantees offered by the reference implementations. As such this implementation may introduce breaking changes, according to the policy declared by the reference implementations. Use at your own risk.
+
+ ℹ️ Public Preview: This is the official Java SDK for GitHub Copilot. This repository treats the official .NET and Node.js SDKs for GitHub Copilot as reference implementations. These SDKs are all officially supported as GitHub open source projects. The Java implementation follows the backward compatibility guarantees offered by the reference implementations. While in public preview, minor breaking changes may still occur between releases.
diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml
index 6a0cdec5b..145629457 100644
--- a/.github/workflows/copilot-setup-steps.yml
+++ b/.github/workflows/copilot-setup-steps.yml
@@ -41,6 +41,10 @@ jobs:
distribution: 'temurin'
cache: 'maven'
+ # Enable repository pre-commit hooks (including Spotless checks for relevant source changes)
+ - name: Enable pre-commit hooks
+ run: git config core.hooksPath .githooks
+
# Verify installations
- name: Verify tool installations
run: |
@@ -50,4 +54,6 @@ jobs:
java -version
gh --version
gh aw version
+ echo "--- Git hooks path ---"
+ git config core.hooksPath
echo "✅ All tools installed successfully"
diff --git a/.github/workflows/notes.template b/.github/workflows/notes.template
index 0fd7af642..9c148cdf1 100644
--- a/.github/workflows/notes.template
+++ b/.github/workflows/notes.template
@@ -1,6 +1,6 @@
# Installation
-⚠️ **Disclaimer:** This is the official Java SDK for GitHub Copilot. This repository treats the official .NET and nodejs SDKs for GitHub Copilot as reference implementations. These SDKS are all officially supported as GitHub open source projects. The Java implementation follows the backward compatibility guarantees offered by the reference implementations. As such this implementation may introduce breaking changes, according to the policy declared by the reference implementations. Use at your own risk.
+ℹ️ **Public Preview:** This is the official Java SDK for GitHub Copilot. This repository treats the official .NET and Node.js SDKs for GitHub Copilot as reference implementations. These SDKs are all officially supported as GitHub open source projects. The Java implementation follows the backward compatibility guarantees offered by the reference implementations. While in public preview, minor breaking changes may still occur between releases.
⚠️ **Artifact versioning plan:** Releases of this implementation track releases of the reference implementation. For each release of the reference implementation, there may follow a corresponding relase of this implementation with the same number as the reference implementation. Release identifiers of the reference implementation are in the form `vMaj.Min.Micro`. For example v0.1.32. The corresponding maven version for the release will be `Maj.Min.Micro-java.N`, where `Maj`, `Min` and `Micro` are the corresponding numbers for the reference impementation release, and `N` is a monotonically increasing sequence number starting with 0 for each release. See the corrseponding architectural decision record for more information in the `docs/adr` directory of the source code.
diff --git a/.github/workflows/publish-maven.yml b/.github/workflows/publish-maven.yml
index a7bb58f01..242853209 100644
--- a/.github/workflows/publish-maven.yml
+++ b/.github/workflows/publish-maven.yml
@@ -121,8 +121,13 @@ jobs:
sed -i "s|[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-java\.[0-9][0-9]*\)\{0,1\}|${VERSION}|g" README.md
sed -i "s|copilot-sdk-java:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-java\.[0-9][0-9]*\)\{0,1\}|copilot-sdk-java:${VERSION}|g" README.md
+ # Update snapshot version in README.md
+ DEV_VERSION="${{ steps.versions.outputs.dev_version }}"
+ sed -i "s|[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-java\.[0-9][0-9]*\)\{0,1\}-SNAPSHOT|${DEV_VERSION}|g" README.md
+
# Update version in jbang-example.java
sed -i "s|copilot-sdk-java:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-java\.[0-9][0-9]*\)\{0,1\}|copilot-sdk-java:${VERSION}|g" jbang-example.java
+ sed -i 's|copilot-sdk-java:${project\.version}|copilot-sdk-java:'"${VERSION}"'|g' jbang-example.java
# Update version in cookbook files (hardcoded for direct GitHub browsing and JBang usage)
find src/site/markdown/cookbook -name "*.md" -type f -exec \
@@ -206,7 +211,7 @@ jobs:
# Build the gh release command
GH_ARGS=("${CURRENT_TAG}")
- GH_ARGS+=("--title" "Copilot Java SDK ${VERSION}")
+ GH_ARGS+=("--title" "GitHub Copilot SDK for Java ${VERSION}")
GH_ARGS+=("--notes" "${RELEASE_NOTES}")
GH_ARGS+=("--generate-notes")
diff --git a/.github/workflows/weekly-upstream-sync.md b/.github/workflows/weekly-upstream-sync.md
index 641f29301..0aaff8d1e 100644
--- a/.github/workflows/weekly-upstream-sync.md
+++ b/.github/workflows/weekly-upstream-sync.md
@@ -41,111 +41,77 @@ safe-outputs:
noop:
report-as-issue: false
---
-# Weekly Upstream Sync Agentic Workflow
-This document describes the `weekly-upstream-sync.yml` GitHub Actions workflow, which automates the detection of new changes in the official [Copilot SDK](https://github.com/github/copilot-sdk) and delegates the merge work to the Copilot coding agent.
+# Weekly Upstream Sync
-## Overview
+You are an automation agent that detects new upstream changes and creates GitHub issues. You do **NOT** perform any code merges, edits, or pushes. Do **NOT** invoke any skills (especially `agentic-merge-upstream`). Your only job is to check for changes and use safe-output tools to create or close issues.
-The workflow runs on a **weekly schedule** (every Monday at 10:00 UTC) and can also be triggered manually. It does **not** perform the actual merge — instead, it detects upstream changes and creates a GitHub issue assigned to `copilot`, instructing the agent to follow the [agentic-merge-upstream](../prompts/agentic-merge-upstream.prompt.md) prompt to port the changes.
+## Instructions
-The agent must also create the Pull Request with the label `upstream-sync`. This allows the workflow to track the merge progress and avoid creating duplicate issues if the agent is still working on a previous sync.
+Follow these steps exactly:
-## Trigger
+### Step 1: Read `.lastmerge`
-| Trigger | Schedule |
-|---|---|
-| `schedule` | Every Monday at 10:00 UTC (`0 10 * * 1`) |
-| `workflow_dispatch` | Manual trigger from the Actions tab |
+Read the file `.lastmerge` in the repository root. It contains the SHA of the last upstream commit that was merged into this Java SDK.
-## Workflow Steps
+### Step 2: Check for upstream changes
-### 1. Checkout repository
+Clone the upstream repository and compare commits:
-Checks out the repo to read the `.lastmerge` file, which contains the SHA of the last upstream commit that was merged into the Java SDK.
-
-### 2. Check for upstream changes
-
-- Reads the last merged commit hash from `.lastmerge`
-- Clones the upstream `github/copilot-sdk` repository
-- Compares `.lastmerge` against upstream `HEAD`
-- If they match: sets `has_changes=false`
-- If they differ: counts new commits, generates a summary (up to 20 most recent), and sets outputs (`commit_count`, `upstream_head`, `last_merge`, `summary`)
-
-### 3. Close previous upstream-sync issues (when changes found)
-
-**Condition:** `has_changes == true`
+```bash
+LAST_MERGE=$(cat .lastmerge)
+git clone --quiet https://github.com/github/copilot-sdk.git /tmp/gh-aw/agent/upstream
+cd /tmp/gh-aw/agent/upstream
+UPSTREAM_HEAD=$(git rev-parse HEAD)
+```
-Before creating a new issue, closes any existing open issues with the `upstream-sync` label. This prevents stale issues from accumulating when previous sync attempts were incomplete or superseded. Each closed issue receives a comment explaining it was superseded.
+If `LAST_MERGE` equals `UPSTREAM_HEAD`, there are **no new changes**. Go to Step 3a.
-### 4. Close stale upstream-sync issues (when no changes found)
+If they differ, count the new commits and generate a summary:
-**Condition:** `has_changes == false`
+```bash
+COMMIT_COUNT=$(git rev-list --count "$LAST_MERGE".."$UPSTREAM_HEAD")
+SUMMARY=$(git log --oneline "$LAST_MERGE".."$UPSTREAM_HEAD" | head -20)
+```
-If the upstream is already up to date, closes any lingering open `upstream-sync` issues with a comment noting that no changes were detected. This handles the case where a previous issue was created but the changes were merged manually (updating `.lastmerge`) before the agent completed.
+Go to Step 3b.
-### 5. Create issue and assign to Copilot
+### Step 3a: No changes detected
-**Condition:** `has_changes == true`
+1. Search for any open issues with the `upstream-sync` label using the GitHub MCP tools.
+2. If there are open `upstream-sync` issues, close each one using the `close_issue` safe-output tool with a comment: "No new upstream changes detected. The Java SDK is up to date. Closing."
+3. Call the `noop` safe-output tool with message: "No new upstream changes since last merge ()."
+4. **Stop here.** Do not proceed further.
-Creates a new GitHub issue with:
+### Step 3b: Changes detected
-- **Title:** `Upstream sync: N new commits (YYYY-MM-DD)`
-- **Label:** `upstream-sync`
-- **Assignee:** `copilot`
-- **Body:** Contains commit count, commit range links, a summary of recent commits, and a link to the merge prompt
+1. Search for any open issues with the `upstream-sync` label using the GitHub MCP tools.
+2. Close each existing open `upstream-sync` issue using the `close_issue` safe-output tool with a comment: "Superseded by a newer upstream sync check."
+3. Create a new issue using the `create_issue` safe-output tool with:
+ - **Title:** `Upstream sync: new commits ()`
+ - **Body:** Include the following information:
+ ```
+ ## Automated Upstream Sync
-The Copilot coding agent picks up the issue, creates a branch and PR, then follows the merge prompt to port the changes.
+ There are **** new commits in the [official Copilot SDK](https://github.com/github/copilot-sdk) since the last merge.
-### 6. Summary
+ - **Last merged commit:** [``](https://github.com/github/copilot-sdk/commit/)
+ - **Upstream HEAD:** [``](https://github.com/github/copilot-sdk/commit/)
-Writes a GitHub Actions step summary with:
+ ### Recent upstream commits
-- Whether changes were detected
-- Commit count and range
-- Recent upstream commits
-- Link to the created issue (if any)
+ ```
+
+ ```
-## Flow Diagram
+ ### Instructions
-```
-┌─────────────────────┐
-│ Schedule / Manual │
-└──────────┬──────────┘
- │
- ▼
-┌─────────────────────┐
-│ Read .lastmerge │
-│ Clone upstream SDK │
-│ Compare commits │
-└──────────┬──────────┘
- │
- ┌─────┴─────┐
- │ │
- changes? no changes
- │ │
- ▼ ▼
-┌──────────┐ ┌──────────────────┐
-│ Close old│ │ Close stale │
-│ issues │ │ issues │
-└────┬─────┘ └──────────────────┘
- │
- ▼
-┌──────────────────────────┐
-│ Create issue assigned to │
-│ copilot │
-└──────────────────────────┘
- │
- ▼
-┌──────────────────────────┐
-│ Agent follows prompt to │
-│ port changes → PR │
-└──────────────────────────┘
-```
+ Follow the [agentic-merge-upstream](.github/prompts/agentic-merge-upstream.prompt.md) prompt to port these changes to the Java SDK.
+ ```
+4. After creating the issue, use the `assign_to_agent` safe-output tool to assign Copilot to the newly created issue.
-## Related Files
+## Important constraints
-| File | Purpose |
-|---|---|
-| `.lastmerge` | Stores the SHA of the last merged upstream commit |
-| [agentic-merge-upstream.prompt.md](../prompts/agentic-merge-upstream.prompt.md) | Detailed instructions the Copilot agent follows to port changes |
-| `.github/scripts/upstream-sync/` | Helper scripts used by the merge prompt |
+- **Do NOT edit any files**, create branches, or push code.
+- **Do NOT invoke any skills** such as `agentic-merge-upstream` or `commit-as-pull-request`.
+- **Do NOT attempt to merge or port upstream changes.** That is done by a separate agent that picks up the issue you create.
+- You **MUST** call at least one safe-output tool (`create_issue`, `close_issue`, `noop`, etc.) before finishing.
diff --git a/.lastmerge b/.lastmerge
index a0cf76b72..83feb636c 100644
--- a/.lastmerge
+++ b/.lastmerge
@@ -1 +1 @@
-40887393a9e687dacc141a645799441b0313ff15
+c3fa6cbfb83d4a20b7912b1a17013d48f5a277a1
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e306db097..38f6e6589 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,30 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]
-> **Upstream sync:** [`github/copilot-sdk@4088739`](https://github.com/github/copilot-sdk/commit/40887393a9e687dacc141a645799441b0313ff15)
+> **Upstream sync:** [`github/copilot-sdk@c3fa6cb`](https://github.com/github/copilot-sdk/commit/c3fa6cbfb83d4a20b7912b1a17013d48f5a277a1)
+
+## [0.2.2-java.1] - 2026-04-07
+
+> **Upstream sync:** [`github/copilot-sdk@c3fa6cb`](https://github.com/github/copilot-sdk/commit/c3fa6cbfb83d4a20b7912b1a17013d48f5a277a1)
+### Added
+
+- Slash commands — register `/command` handlers invoked from the CLI TUI via `SessionConfig.setCommands()` (upstream: [`f7fd757`](https://github.com/github/copilot-sdk/commit/f7fd757))
+- `CommandDefinition`, `CommandContext`, `CommandHandler`, `CommandWireDefinition` — types for defining and handling slash commands
+- `CommandExecuteEvent` — event dispatched when a registered slash command is executed
+- Elicitation (UI dialogs) — incoming handler via `SessionConfig.setOnElicitationRequest()` and outgoing convenience methods via `session.getUi()` (upstream: [`f7fd757`](https://github.com/github/copilot-sdk/commit/f7fd757))
+- `ElicitationContext`, `ElicitationHandler`, `ElicitationParams`, `ElicitationResult`, `ElicitationResultAction`, `ElicitationSchema`, `InputOptions` — types for elicitation
+- `ElicitationRequestedEvent` — event dispatched when an elicitation request is received
+- `SessionUiApi` — convenience API on `session.getUi()` for `confirm()`, `select()`, `input()`, and `elicitation()` calls
+- `SessionCapabilities` and `SessionUiCapabilities` — session capability reporting populated from create/resume response
+- `CapabilitiesChangedEvent` — event dispatched when session capabilities are updated
+- `CopilotClient.getSessionMetadata(String)` — O(1) session lookup by ID
+- `GetSessionMetadataResponse` — response type for `getSessionMetadata`
+
+### Fixed
+
+- Permission events already resolved by a pre-hook now short-circuit before invoking the client-side handler
+- `SessionUiApi` Javadoc now uses valid Java null-check syntax instead of `?.`
+- README updated to say "GitHub Copilot CLI 1.0.17" instead of "GitHub Copilot 1.0.17"
## [0.2.1-java.1] - 2026-04-02
@@ -465,16 +488,22 @@ New types: `GetForegroundSessionResponse`, `SetForegroundSessionResponse`
- Pre-commit hook for Spotless code formatting
- Comprehensive API documentation
-[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...HEAD
+[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.2-java.1...HEAD
+[0.2.2-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...v0.2.2-java.1
+[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.2-java.1...HEAD
+[0.2.2-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...v0.2.2-java.1
[0.2.1-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.0...v0.2.1-java.1
-[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...HEAD
+[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.2-java.1...HEAD
+[0.2.2-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...v0.2.2-java.1
[0.2.1-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.0...v0.2.1-java.1
[0.2.1-java.0]: https://github.com/github/copilot-sdk-java/compare/v0.1.32-java.0...v0.2.1-java.0
-[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...HEAD
+[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.2-java.1...HEAD
+[0.2.2-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...v0.2.2-java.1
[0.2.1-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.0...v0.2.1-java.1
[0.2.1-java.0]: https://github.com/github/copilot-sdk-java/compare/v0.1.32-java.0...v0.2.1-java.0
[0.1.32-java.0]: https://github.com/github/copilot-sdk-java/compare/v1.0.11...v0.1.32-java.0
-[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...HEAD
+[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.2-java.1...HEAD
+[0.2.2-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...v0.2.2-java.1
[0.2.1-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.0...v0.2.1-java.1
[0.2.1-java.0]: https://github.com/github/copilot-sdk-java/compare/v0.1.32-java.0...v0.2.1-java.0
[0.1.32-java.0]: https://github.com/github/copilot-sdk-java/compare/v1.0.11...v0.1.32-java.0
diff --git a/README.md b/README.md
index 3010b6839..a33aaedda 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@
## Background
-> ⚠️ **Disclaimer:** This SDK tracks the pre-GA [GitHub Copilot SDKs](https://github.com/github/copilot-sdk) for [.NET](https://github.com/github/copilot-sdk/tree/main/dotnet) and [nodejs](https://github.com/github/copilot-sdk/tree/main/nodejs). This SDK may change in breaking ways. Use at your own risk.
+> ℹ️ **Public Preview:** This SDK tracks the [GitHub Copilot SDKs](https://github.com/github/copilot-sdk) for [.NET](https://github.com/github/copilot-sdk/tree/main/dotnet) and [Node.js](https://github.com/github/copilot-sdk/tree/main/nodejs). While in public preview, minor breaking changes may still occur between releases.
Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build AI-powered applications and agentic workflows.
@@ -25,7 +25,7 @@ Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build A
### Requirements
- Java 17 or later. **JDK 25 recommended**. Selecting JDK 25 enables the use of virtual threads, as shown in the [Quick Start](#quick-start).
-- GitHub Copilot 1.0.15-0 or later installed and in `PATH` (or provide custom `cliPath`)
+- GitHub Copilot CLI 1.0.17 or later installed and in `PATH` (or provide custom `cliPath`)
### Maven
@@ -33,7 +33,7 @@ Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build A
com.githubcopilot-sdk-java
- 0.2.1-java.1
+ 0.2.2-java.1
```
@@ -53,14 +53,14 @@ Snapshot builds of the next development version are published to Maven Central S
com.githubcopilot-sdk-java
- 0.2.1-java.0-SNAPSHOT
+ 0.2.3-java.1-SNAPSHOT
```
### Gradle
```groovy
-implementation 'com.github:copilot-sdk-java:0.2.1-java.1'
+implementation 'com.github:copilot-sdk-java:0.2.2-java.1'
```
## Quick Start
diff --git a/instructions/copilot-sdk-java.instructions.md b/instructions/copilot-sdk-java.instructions.md
index bf18a3c5a..7881322fd 100644
--- a/instructions/copilot-sdk-java.instructions.md
+++ b/instructions/copilot-sdk-java.instructions.md
@@ -6,7 +6,7 @@ name: 'GitHub Copilot SDK Java Instructions'
## Core Principles
-- The SDK is in technical preview and may have breaking changes
+- The SDK is in public preview and may have breaking changes
- Requires Java 17 or later
- Requires GitHub Copilot CLI installed and in PATH
- Uses `CompletableFuture` for all async operations
diff --git a/jbang-example.java b/jbang-example.java
index 3d02653c1..dd1f80762 100644
--- a/jbang-example.java
+++ b/jbang-example.java
@@ -1,5 +1,5 @@
!
-//DEPS com.github:copilot-sdk-java:${project.version}
+//DEPS com.github:copilot-sdk-java:0.2.2-java.1
import com.github.copilot.sdk.CopilotClient;
import com.github.copilot.sdk.events.AssistantMessageEvent;
import com.github.copilot.sdk.events.SessionUsageInfoEvent;
diff --git a/pom.xml b/pom.xml
index 43e1b436d..b61c36166 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
com.githubcopilot-sdk-java
- 0.2.1-java.1
+ 0.2.3-java.1-SNAPSHOTjarGitHub Copilot SDK :: Java
@@ -33,7 +33,7 @@
scm:git:https://github.com/github/copilot-sdk-java.gitscm:git:https://github.com/github/copilot-sdk-java.githttps://github.com/github/copilot-sdk-java
- v0.2.1-java.1
+ HEAD
@@ -51,6 +51,8 @@
${copilot.sdk.clone.dir}/testfalse
+
+
@@ -89,7 +91,7 @@
org.mockitomockito-core
- 5.17.0
+ 5.23.0test
@@ -245,8 +247,8 @@
maven-surefire-plugin3.5.4
-
- ${testExecutionAgentArgs}
+
+ ${testExecutionAgentArgs} ${surefire.jvm.args}${copilot.tests.dir}${copilot.sdk.clone.dir}
@@ -543,6 +545,18 @@
+
+
+ jdk21+
+
+ [21,)
+
+
+ -XX:+EnableDynamicAgentLoading
+
+ skip-test-harness
diff --git a/src/main/java/com/github/copilot/sdk/CopilotClient.java b/src/main/java/com/github/copilot/sdk/CopilotClient.java
index e2790f6a3..f00e2fd11 100644
--- a/src/main/java/com/github/copilot/sdk/CopilotClient.java
+++ b/src/main/java/com/github/copilot/sdk/CopilotClient.java
@@ -24,6 +24,7 @@
import com.github.copilot.sdk.json.DeleteSessionResponse;
import com.github.copilot.sdk.json.GetAuthStatusResponse;
import com.github.copilot.sdk.json.GetLastSessionIdResponse;
+import com.github.copilot.sdk.json.GetSessionMetadataResponse;
import com.github.copilot.sdk.json.GetModelsResponse;
import com.github.copilot.sdk.json.GetStatusResponse;
import com.github.copilot.sdk.json.ListSessionsResponse;
@@ -374,6 +375,7 @@ public CompletableFuture createSession(SessionConfig config) {
return connection.rpc.invoke("session.create", request, CreateSessionResponse.class).thenApply(response -> {
session.setWorkspacePath(response.workspacePath());
+ session.setCapabilities(response.capabilities());
// If the server returned a different sessionId (e.g. a v2 CLI that ignores
// the client-supplied ID), re-key the sessions map.
String returnedId = response.sessionId();
@@ -444,6 +446,7 @@ public CompletableFuture resumeSession(String sessionId, ResumeS
return connection.rpc.invoke("session.resume", request, ResumeSessionResponse.class).thenApply(response -> {
session.setWorkspacePath(response.workspacePath());
+ session.setCapabilities(response.capabilities());
// If the server returned a different sessionId than what was requested, re-key.
String returnedId = response.sessionId();
if (returnedId != null && !returnedId.equals(sessionId)) {
@@ -657,6 +660,34 @@ public CompletableFuture> listSessions(SessionListFilter f
});
}
+ /**
+ * Gets metadata for a specific session by ID.
+ *
+ * This provides an efficient O(1) lookup of a single session's metadata instead
+ * of listing all sessions.
+ *
+ *
Example Usage
+ *
+ *
{@code
+ * var metadata = client.getSessionMetadata("session-123").get();
+ * if (metadata != null) {
+ * System.out.println("Session started at: " + metadata.getStartTime());
+ * }
+ * }
+ *
+ * @param sessionId
+ * the ID of the session to look up
+ * @return a future that resolves with the {@link SessionMetadata}, or
+ * {@code null} if the session was not found
+ * @see SessionMetadata
+ * @since 1.0.0
+ */
+ public CompletableFuture getSessionMetadata(String sessionId) {
+ return ensureConnected().thenCompose(connection -> connection.rpc
+ .invoke("session.getMetadata", Map.of("sessionId", sessionId), GetSessionMetadataResponse.class)
+ .thenApply(GetSessionMetadataResponse::session));
+ }
+
/**
* Gets the ID of the session currently displayed in the TUI.
*
diff --git a/src/main/java/com/github/copilot/sdk/CopilotSession.java b/src/main/java/com/github/copilot/sdk/CopilotSession.java
index 844737fc2..23b1b5368 100644
--- a/src/main/java/com/github/copilot/sdk/CopilotSession.java
+++ b/src/main/java/com/github/copilot/sdk/CopilotSession.java
@@ -31,14 +31,27 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.copilot.sdk.events.AbstractSessionEvent;
import com.github.copilot.sdk.events.AssistantMessageEvent;
+import com.github.copilot.sdk.events.CapabilitiesChangedEvent;
+import com.github.copilot.sdk.events.CommandExecuteEvent;
+import com.github.copilot.sdk.events.ElicitationRequestedEvent;
import com.github.copilot.sdk.events.ExternalToolRequestedEvent;
import com.github.copilot.sdk.events.PermissionRequestedEvent;
import com.github.copilot.sdk.events.SessionErrorEvent;
import com.github.copilot.sdk.events.SessionEventParser;
import com.github.copilot.sdk.events.SessionIdleEvent;
import com.github.copilot.sdk.json.AgentInfo;
+import com.github.copilot.sdk.json.CommandContext;
+import com.github.copilot.sdk.json.CommandDefinition;
+import com.github.copilot.sdk.json.CommandHandler;
+import com.github.copilot.sdk.json.ElicitationContext;
+import com.github.copilot.sdk.json.ElicitationHandler;
+import com.github.copilot.sdk.json.ElicitationParams;
+import com.github.copilot.sdk.json.ElicitationResult;
+import com.github.copilot.sdk.json.ElicitationResultAction;
+import com.github.copilot.sdk.json.ElicitationSchema;
import com.github.copilot.sdk.json.GetMessagesResponse;
import com.github.copilot.sdk.json.HookInvocation;
+import com.github.copilot.sdk.json.InputOptions;
import com.github.copilot.sdk.json.MessageOptions;
import com.github.copilot.sdk.json.PermissionHandler;
import com.github.copilot.sdk.json.PermissionInvocation;
@@ -49,9 +62,12 @@
import com.github.copilot.sdk.json.PreToolUseHookInput;
import com.github.copilot.sdk.json.SendMessageRequest;
import com.github.copilot.sdk.json.SendMessageResponse;
+import com.github.copilot.sdk.json.SessionCapabilities;
import com.github.copilot.sdk.json.SessionEndHookInput;
import com.github.copilot.sdk.json.SessionHooks;
import com.github.copilot.sdk.json.SessionStartHookInput;
+import com.github.copilot.sdk.json.SessionUiApi;
+import com.github.copilot.sdk.json.SessionUiCapabilities;
import com.github.copilot.sdk.json.ToolDefinition;
import com.github.copilot.sdk.json.ToolResultObject;
import com.github.copilot.sdk.json.UserInputHandler;
@@ -116,11 +132,15 @@ public final class CopilotSession implements AutoCloseable {
*/
private volatile String sessionId;
private volatile String workspacePath;
+ private volatile SessionCapabilities capabilities = new SessionCapabilities();
+ private final SessionUiApi ui;
private final JsonRpcClient rpc;
private final Set> eventHandlers = ConcurrentHashMap.newKeySet();
private final Map toolHandlers = new ConcurrentHashMap<>();
+ private final Map commandHandlers = new ConcurrentHashMap<>();
private final AtomicReference permissionHandler = new AtomicReference<>();
private final AtomicReference userInputHandler = new AtomicReference<>();
+ private final AtomicReference elicitationHandler = new AtomicReference<>();
private final AtomicReference hooksHandler = new AtomicReference<>();
private volatile EventErrorHandler eventErrorHandler;
private volatile EventErrorPolicy eventErrorPolicy = EventErrorPolicy.PROPAGATE_AND_LOG_ERRORS;
@@ -163,6 +183,7 @@ public final class CopilotSession implements AutoCloseable {
this.sessionId = sessionId;
this.rpc = rpc;
this.workspacePath = workspacePath;
+ this.ui = new SessionUiApiImpl();
var executor = new ScheduledThreadPoolExecutor(1, r -> {
var t = new Thread(r, "sendAndWait-timeout");
t.setDaemon(true);
@@ -225,6 +246,30 @@ void setWorkspacePath(String workspacePath) {
this.workspacePath = workspacePath;
}
+ /**
+ * Gets the capabilities reported by the host for this session.
+ *
+ * Capabilities are populated from the session create/resume response and
+ * updated in real time via {@code capabilities.changed} events.
+ *
+ * @return the session capabilities (never {@code null})
+ */
+ public SessionCapabilities getCapabilities() {
+ return capabilities;
+ }
+
+ /**
+ * Gets the UI API for eliciting information from the user during this session.
+ *
+ * All methods on this API throw {@link IllegalStateException} if the host does
+ * not report elicitation support via {@link #getCapabilities()}.
+ *
+ * @return the UI API
+ */
+ public SessionUiApi getUi() {
+ return ui;
+ }
+
/**
* Sets a custom error handler for exceptions thrown by event handlers.
*
+ * Called internally when creating or resuming a session with commands.
+ *
+ * @param commands
+ * the command definitions to register
+ */
+ void registerCommands(java.util.List commands) {
+ commandHandlers.clear();
+ if (commands != null) {
+ for (CommandDefinition cmd : commands) {
+ if (cmd.getName() != null && cmd.getHandler() != null) {
+ commandHandlers.put(cmd.getName(), cmd.getHandler());
+ }
+ }
+ }
+ }
+
+ /**
+ * Registers an elicitation handler for this session.
+ *
+ * Called internally when creating or resuming a session with an elicitation
+ * handler.
+ *
+ * @param handler
+ * the handler to invoke when an elicitation request is received
+ */
+ void registerElicitationHandler(ElicitationHandler handler) {
+ elicitationHandler.set(handler);
+ }
+
+ /**
+ * Sets the capabilities reported by the host for this session.
+ *
+ * Called internally after session create/resume response.
+ *
+ * @param sessionCapabilities
+ * the capabilities to set, or {@code null} for empty capabilities
+ */
+ void setCapabilities(SessionCapabilities sessionCapabilities) {
+ this.capabilities = sessionCapabilities != null ? sessionCapabilities : new SessionCapabilities();
+ }
+
/**
* Handles a user input request from the Copilot CLI.
*
+ * Broadcast when the host's session capabilities change. The SDK updates
+ * {@link com.github.copilot.sdk.CopilotSession#getCapabilities()} accordingly.
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class CapabilitiesChangedEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private CapabilitiesChangedData data;
+
+ @Override
+ public String getType() {
+ return "capabilities.changed";
+ }
+
+ public CapabilitiesChangedData getData() {
+ return data;
+ }
+
+ public void setData(CapabilitiesChangedData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record CapabilitiesChangedData(@JsonProperty("ui") CapabilitiesChangedUi ui) {
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record CapabilitiesChangedUi(@JsonProperty("elicitation") Boolean elicitation) {
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/CommandExecuteEvent.java b/src/main/java/com/github/copilot/sdk/events/CommandExecuteEvent.java
new file mode 100644
index 000000000..c08c4a88d
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/CommandExecuteEvent.java
@@ -0,0 +1,43 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.events;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Event: command.execute
+ *
+ * Broadcast when the user executes a slash command registered by this client.
+ * Clients that have a matching command handler should respond via
+ * {@code session.commands.handlePendingCommand}.
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class CommandExecuteEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private CommandExecuteData data;
+
+ @Override
+ public String getType() {
+ return "command.execute";
+ }
+
+ public CommandExecuteData getData() {
+ return data;
+ }
+
+ public void setData(CommandExecuteData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record CommandExecuteData(@JsonProperty("requestId") String requestId,
+ @JsonProperty("command") String command, @JsonProperty("commandName") String commandName,
+ @JsonProperty("args") String args) {
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/ElicitationRequestedEvent.java b/src/main/java/com/github/copilot/sdk/events/ElicitationRequestedEvent.java
new file mode 100644
index 000000000..e459dfb77
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/ElicitationRequestedEvent.java
@@ -0,0 +1,54 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.events;
+
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Event: elicitation.requested
+ *
+ * Broadcast when the server or an MCP tool requests structured input from the
+ * user. Clients that have an elicitation handler should respond via
+ * {@code session.ui.handlePendingElicitation}.
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class ElicitationRequestedEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private ElicitationRequestedData data;
+
+ @Override
+ public String getType() {
+ return "elicitation.requested";
+ }
+
+ public ElicitationRequestedData getData() {
+ return data;
+ }
+
+ public void setData(ElicitationRequestedData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record ElicitationRequestedData(@JsonProperty("requestId") String requestId,
+ @JsonProperty("toolCallId") String toolCallId, @JsonProperty("elicitationSource") String elicitationSource,
+ @JsonProperty("message") String message, @JsonProperty("mode") String mode,
+ @JsonProperty("requestedSchema") ElicitationRequestedSchema requestedSchema,
+ @JsonProperty("url") String url) {
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record ElicitationRequestedSchema(@JsonProperty("type") String type,
+ @JsonProperty("properties") Map properties,
+ @JsonProperty("required") List required) {
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/PermissionRequestedEvent.java b/src/main/java/com/github/copilot/sdk/events/PermissionRequestedEvent.java
index d8f9ec147..7ebce5ac7 100644
--- a/src/main/java/com/github/copilot/sdk/events/PermissionRequestedEvent.java
+++ b/src/main/java/com/github/copilot/sdk/events/PermissionRequestedEvent.java
@@ -38,6 +38,7 @@ public void setData(PermissionRequestedData data) {
@JsonIgnoreProperties(ignoreUnknown = true)
public record PermissionRequestedData(@JsonProperty("requestId") String requestId,
- @JsonProperty("permissionRequest") PermissionRequest permissionRequest) {
+ @JsonProperty("permissionRequest") PermissionRequest permissionRequest,
+ @JsonProperty("resolvedByHook") Boolean resolvedByHook) {
}
}
diff --git a/src/main/java/com/github/copilot/sdk/events/SessionEventParser.java b/src/main/java/com/github/copilot/sdk/events/SessionEventParser.java
index 308317e6b..dda971769 100644
--- a/src/main/java/com/github/copilot/sdk/events/SessionEventParser.java
+++ b/src/main/java/com/github/copilot/sdk/events/SessionEventParser.java
@@ -99,6 +99,9 @@ public class SessionEventParser {
TYPE_MAP.put("permission.completed", PermissionCompletedEvent.class);
TYPE_MAP.put("command.queued", CommandQueuedEvent.class);
TYPE_MAP.put("command.completed", CommandCompletedEvent.class);
+ TYPE_MAP.put("command.execute", CommandExecuteEvent.class);
+ TYPE_MAP.put("elicitation.requested", ElicitationRequestedEvent.class);
+ TYPE_MAP.put("capabilities.changed", CapabilitiesChangedEvent.class);
TYPE_MAP.put("exit_plan_mode.requested", ExitPlanModeRequestedEvent.class);
TYPE_MAP.put("exit_plan_mode.completed", ExitPlanModeCompletedEvent.class);
TYPE_MAP.put("system.notification", SystemNotificationEvent.class);
diff --git a/src/main/java/com/github/copilot/sdk/json/CommandContext.java b/src/main/java/com/github/copilot/sdk/json/CommandContext.java
new file mode 100644
index 000000000..4657699bb
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/json/CommandContext.java
@@ -0,0 +1,74 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.json;
+
+/**
+ * Context passed to a {@link CommandHandler} when a slash command is executed.
+ *
+ * @since 1.0.0
+ */
+public class CommandContext {
+
+ private String sessionId;
+ private String command;
+ private String commandName;
+ private String args;
+
+ /** Gets the session ID where the command was invoked. @return the session ID */
+ public String getSessionId() {
+ return sessionId;
+ }
+
+ /** Sets the session ID. @param sessionId the session ID @return this */
+ public CommandContext setSessionId(String sessionId) {
+ this.sessionId = sessionId;
+ return this;
+ }
+
+ /**
+ * Gets the full command text (e.g., {@code /deploy production}).
+ *
+ * @return the full command text
+ */
+ public String getCommand() {
+ return command;
+ }
+
+ /** Sets the full command text. @param command the command text @return this */
+ public CommandContext setCommand(String command) {
+ this.command = command;
+ return this;
+ }
+
+ /**
+ * Gets the command name without the leading {@code /}.
+ *
+ * @return the command name
+ */
+ public String getCommandName() {
+ return commandName;
+ }
+
+ /** Sets the command name. @param commandName the command name @return this */
+ public CommandContext setCommandName(String commandName) {
+ this.commandName = commandName;
+ return this;
+ }
+
+ /**
+ * Gets the raw argument string after the command name.
+ *
+ * @return the argument string
+ */
+ public String getArgs() {
+ return args;
+ }
+
+ /** Sets the argument string. @param args the argument string @return this */
+ public CommandContext setArgs(String args) {
+ this.args = args;
+ return this;
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/json/CommandDefinition.java b/src/main/java/com/github/copilot/sdk/json/CommandDefinition.java
new file mode 100644
index 000000000..33a6cbada
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/json/CommandDefinition.java
@@ -0,0 +1,98 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.json;
+
+/**
+ * Defines a slash command that users can invoke from the CLI TUI.
+ *
+ * Register commands via {@link SessionConfig#setCommands(java.util.List)} or
+ * {@link ResumeSessionConfig#setCommands(java.util.List)}. Each command appears
+ * as {@code /name} in the CLI TUI.
+ *
+ *
Example Usage
+ *
+ *
{@code
+ * var config = new SessionConfig().setCommands(List.of(
+ * new CommandDefinition().setName("deploy").setDescription("Deploy the application").setHandler(context -> {
+ * System.out.println("Deploying: " + context.getArgs());
+ * return CompletableFuture.completedFuture(null);
+ * })));
+ * }
+ *
+ * @see CommandHandler
+ * @see CommandContext
+ * @since 1.0.0
+ */
+public class CommandDefinition {
+
+ private String name;
+ private String description;
+ private CommandHandler handler;
+
+ /**
+ * Gets the command name (without leading {@code /}).
+ *
+ * @return the command name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the command name (without leading {@code /}).
+ *
+ * For example, {@code "deploy"} registers the {@code /deploy} command.
+ *
+ * @param name
+ * the command name
+ * @return this instance for method chaining
+ */
+ public CommandDefinition setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * Gets the human-readable description shown in the command completion UI.
+ *
+ * @return the description, or {@code null} if not set
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Sets the human-readable description shown in the command completion UI.
+ *
+ * @param description
+ * the description
+ * @return this instance for method chaining
+ */
+ public CommandDefinition setDescription(String description) {
+ this.description = description;
+ return this;
+ }
+
+ /**
+ * Gets the handler invoked when the command is executed.
+ *
+ * @return the command handler
+ */
+ public CommandHandler getHandler() {
+ return handler;
+ }
+
+ /**
+ * Sets the handler invoked when the command is executed.
+ *
+ * @param handler
+ * the command handler
+ * @return this instance for method chaining
+ */
+ public CommandDefinition setHandler(CommandHandler handler) {
+ this.handler = handler;
+ return this;
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/json/CommandHandler.java b/src/main/java/com/github/copilot/sdk/json/CommandHandler.java
new file mode 100644
index 000000000..d63955638
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/json/CommandHandler.java
@@ -0,0 +1,41 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.json;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Functional interface for handling slash-command executions.
+ *
+ * Implement this interface to define the behavior of a registered slash
+ * command. The handler is invoked when the user executes the command in the CLI
+ * TUI.
+ *
+ *
+ *
+ * @see CommandDefinition
+ * @since 1.0.0
+ */
+@FunctionalInterface
+public interface CommandHandler {
+
+ /**
+ * Handles a slash-command execution.
+ *
+ * @param context
+ * the command context containing session ID, command text, and
+ * arguments
+ * @return a future that completes when the command handling is done
+ */
+ CompletableFuture handle(CommandContext context);
+}
diff --git a/src/main/java/com/github/copilot/sdk/json/CommandWireDefinition.java b/src/main/java/com/github/copilot/sdk/json/CommandWireDefinition.java
new file mode 100644
index 000000000..2ee65c58e
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/json/CommandWireDefinition.java
@@ -0,0 +1,58 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.json;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Wire-format representation of a command definition for RPC serialization.
+ *
+ * This is a low-level class used internally. Use {@link CommandDefinition} to
+ * define commands for a session.
+ *
+ * @since 1.0.0
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public final class CommandWireDefinition {
+
+ @JsonProperty("name")
+ private String name;
+
+ @JsonProperty("description")
+ private String description;
+
+ /** Creates an empty definition. */
+ public CommandWireDefinition() {
+ }
+
+ /** Creates a definition with name and description. */
+ public CommandWireDefinition(String name, String description) {
+ this.name = name;
+ this.description = description;
+ }
+
+ /** Gets the command name. @return the name */
+ public String getName() {
+ return name;
+ }
+
+ /** Sets the command name. @param name the name @return this */
+ public CommandWireDefinition setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /** Gets the description. @return the description */
+ public String getDescription() {
+ return description;
+ }
+
+ /** Sets the description. @param description the description @return this */
+ public CommandWireDefinition setDescription(String description) {
+ this.description = description;
+ return this;
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/json/CreateSessionRequest.java b/src/main/java/com/github/copilot/sdk/json/CreateSessionRequest.java
index c0243f14b..d030631de 100644
--- a/src/main/java/com/github/copilot/sdk/json/CreateSessionRequest.java
+++ b/src/main/java/com/github/copilot/sdk/json/CreateSessionRequest.java
@@ -91,6 +91,12 @@ public final class CreateSessionRequest {
@JsonProperty("configDir")
private String configDir;
+ @JsonProperty("commands")
+ private List commands;
+
+ @JsonProperty("requestElicitation")
+ private Boolean requestElicitation;
+
/** Gets the model name. @return the model */
public String getModel() {
return model;
@@ -312,4 +318,24 @@ public String getConfigDir() {
public void setConfigDir(String configDir) {
this.configDir = configDir;
}
+
+ /** Gets the commands wire definitions. @return the commands */
+ public List getCommands() {
+ return commands == null ? null : Collections.unmodifiableList(commands);
+ }
+
+ /** Sets the commands wire definitions. @param commands the commands */
+ public void setCommands(List commands) {
+ this.commands = commands;
+ }
+
+ /** Gets the requestElicitation flag. @return the flag */
+ public Boolean getRequestElicitation() {
+ return requestElicitation;
+ }
+
+ /** Sets the requestElicitation flag. @param requestElicitation the flag */
+ public void setRequestElicitation(Boolean requestElicitation) {
+ this.requestElicitation = requestElicitation;
+ }
}
diff --git a/src/main/java/com/github/copilot/sdk/json/CreateSessionResponse.java b/src/main/java/com/github/copilot/sdk/json/CreateSessionResponse.java
index 5b1a177f0..b47af050b 100644
--- a/src/main/java/com/github/copilot/sdk/json/CreateSessionResponse.java
+++ b/src/main/java/com/github/copilot/sdk/json/CreateSessionResponse.java
@@ -11,9 +11,12 @@
* @param workspacePath
* the workspace path, or {@code null} if infinite sessions are
* disabled
+ * @param capabilities
+ * the capabilities reported by the host, or {@code null}
* @since 1.0.0
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public record CreateSessionResponse(@JsonProperty("sessionId") String sessionId,
- @JsonProperty("workspacePath") String workspacePath) {
+ @JsonProperty("workspacePath") String workspacePath,
+ @JsonProperty("capabilities") SessionCapabilities capabilities) {
}
diff --git a/src/main/java/com/github/copilot/sdk/json/ElicitationContext.java b/src/main/java/com/github/copilot/sdk/json/ElicitationContext.java
new file mode 100644
index 000000000..87687b194
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/json/ElicitationContext.java
@@ -0,0 +1,112 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.json;
+
+/**
+ * Context for an elicitation request received from the server or MCP tools.
+ *
+ * @since 1.0.0
+ */
+public class ElicitationContext {
+
+ private String sessionId;
+ private String message;
+ private ElicitationSchema requestedSchema;
+ private String mode;
+ private String elicitationSource;
+ private String url;
+
+ /**
+ * Gets the session ID that triggered the elicitation request. @return the
+ * session ID
+ */
+ public String getSessionId() {
+ return sessionId;
+ }
+
+ /** Sets the session ID. @param sessionId the session ID @return this */
+ public ElicitationContext setSessionId(String sessionId) {
+ this.sessionId = sessionId;
+ return this;
+ }
+
+ /**
+ * Gets the message describing what information is needed from the user.
+ *
+ * @return the message
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /** Sets the message. @param message the message @return this */
+ public ElicitationContext setMessage(String message) {
+ this.message = message;
+ return this;
+ }
+
+ /**
+ * Gets the JSON Schema describing the form fields to present (form mode only).
+ *
+ * @return the schema, or {@code null}
+ */
+ public ElicitationSchema getRequestedSchema() {
+ return requestedSchema;
+ }
+
+ /** Sets the schema. @param requestedSchema the schema @return this */
+ public ElicitationContext setRequestedSchema(ElicitationSchema requestedSchema) {
+ this.requestedSchema = requestedSchema;
+ return this;
+ }
+
+ /**
+ * Gets the elicitation mode: {@code "form"} for structured input, {@code "url"}
+ * for browser redirect.
+ *
+ * @return the mode, or {@code null} (defaults to {@code "form"})
+ */
+ public String getMode() {
+ return mode;
+ }
+
+ /** Sets the mode. @param mode the mode @return this */
+ public ElicitationContext setMode(String mode) {
+ this.mode = mode;
+ return this;
+ }
+
+ /**
+ * Gets the source that initiated the request (e.g., MCP server name).
+ *
+ * @return the elicitation source, or {@code null}
+ */
+ public String getElicitationSource() {
+ return elicitationSource;
+ }
+
+ /**
+ * Sets the elicitation source. @param elicitationSource the source @return this
+ */
+ public ElicitationContext setElicitationSource(String elicitationSource) {
+ this.elicitationSource = elicitationSource;
+ return this;
+ }
+
+ /**
+ * Gets the URL to open in the user's browser (url mode only).
+ *
+ * @return the URL, or {@code null}
+ */
+ public String getUrl() {
+ return url;
+ }
+
+ /** Sets the URL. @param url the URL @return this */
+ public ElicitationContext setUrl(String url) {
+ this.url = url;
+ return this;
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/json/ElicitationHandler.java b/src/main/java/com/github/copilot/sdk/json/ElicitationHandler.java
new file mode 100644
index 000000000..d0a0d0616
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/json/ElicitationHandler.java
@@ -0,0 +1,44 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.json;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Functional interface for handling elicitation requests from the server.
+ *
+ * Register an elicitation handler via
+ * {@link SessionConfig#setOnElicitationRequest(ElicitationHandler)} or
+ * {@link ResumeSessionConfig#setOnElicitationRequest(ElicitationHandler)}. When
+ * provided, the server routes elicitation requests to this handler and reports
+ * elicitation as a supported capability.
+ *
+ *
Example Usage
+ *
+ *
{@code
+ * ElicitationHandler handler = context -> {
+ * // Show the form to the user and collect responses
+ * Map formValues = showForm(context.getMessage(), context.getRequestedSchema());
+ * return CompletableFuture.completedFuture(
+ * new ElicitationResult().setAction(ElicitationResultAction.ACCEPT).setContent(formValues));
+ * };
+ * }
+ *
+ * @see ElicitationContext
+ * @see ElicitationResult
+ * @since 1.0.0
+ */
+@FunctionalInterface
+public interface ElicitationHandler {
+
+ /**
+ * Handles an elicitation request from the server.
+ *
+ * @param context
+ * the elicitation context containing the message, schema, and mode
+ * @return a future that resolves with the elicitation result
+ */
+ CompletableFuture handle(ElicitationContext context);
+}
diff --git a/src/main/java/com/github/copilot/sdk/json/ElicitationParams.java b/src/main/java/com/github/copilot/sdk/json/ElicitationParams.java
new file mode 100644
index 000000000..8bd81022e
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/json/ElicitationParams.java
@@ -0,0 +1,58 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.json;
+
+/**
+ * Parameters for an elicitation request sent from the SDK to the host.
+ *
+ * @since 1.0.0
+ */
+public class ElicitationParams {
+
+ private String message;
+ private ElicitationSchema requestedSchema;
+
+ /**
+ * Gets the message describing what information is needed from the user.
+ *
+ * @return the message
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Sets the message describing what information is needed from the user.
+ *
+ * @param message
+ * the message
+ * @return this instance for method chaining
+ */
+ public ElicitationParams setMessage(String message) {
+ this.message = message;
+ return this;
+ }
+
+ /**
+ * Gets the JSON Schema describing the form fields to present.
+ *
+ * @return the requested schema
+ */
+ public ElicitationSchema getRequestedSchema() {
+ return requestedSchema;
+ }
+
+ /**
+ * Sets the JSON Schema describing the form fields to present.
+ *
+ * @param requestedSchema
+ * the schema
+ * @return this instance for method chaining
+ */
+ public ElicitationParams setRequestedSchema(ElicitationSchema requestedSchema) {
+ this.requestedSchema = requestedSchema;
+ return this;
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/json/ElicitationResult.java b/src/main/java/com/github/copilot/sdk/json/ElicitationResult.java
new file mode 100644
index 000000000..3ba30b83d
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/json/ElicitationResult.java
@@ -0,0 +1,68 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.json;
+
+import java.util.Map;
+
+/**
+ * Result returned from an elicitation dialog.
+ *
+ * @since 1.0.0
+ */
+public class ElicitationResult {
+
+ private ElicitationResultAction action;
+ private Map content;
+
+ /**
+ * Gets the user action taken on the elicitation dialog.
+ *
+ * {@link ElicitationResultAction#ACCEPT} means the user submitted the form,
+ * {@link ElicitationResultAction#DECLINE} means the user rejected the request,
+ * and {@link ElicitationResultAction#CANCEL} means the user dismissed the
+ * dialog.
+ *
+ * @return the user action
+ */
+ public ElicitationResultAction getAction() {
+ return action;
+ }
+
+ /**
+ * Sets the user action taken on the elicitation dialog.
+ *
+ * @param action
+ * the user action
+ * @return this instance for method chaining
+ */
+ public ElicitationResult setAction(ElicitationResultAction action) {
+ this.action = action;
+ return this;
+ }
+
+ /**
+ * Gets the form values submitted by the user.
+ *
+ * Only present when {@link #getAction()} is
+ * {@link ElicitationResultAction#ACCEPT}.
+ *
+ * @return the submitted form values, or {@code null} if the user did not accept
+ */
+ public Map getContent() {
+ return content;
+ }
+
+ /**
+ * Sets the form values submitted by the user.
+ *
+ * @param content
+ * the submitted form values
+ * @return this instance for method chaining
+ */
+ public ElicitationResult setContent(Map content) {
+ this.content = content;
+ return this;
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/json/ElicitationResultAction.java b/src/main/java/com/github/copilot/sdk/json/ElicitationResultAction.java
new file mode 100644
index 000000000..fd280cdeb
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/json/ElicitationResultAction.java
@@ -0,0 +1,33 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.json;
+
+/**
+ * Action value for an {@link ElicitationResult}.
+ *
+ * @since 1.0.0
+ */
+public enum ElicitationResultAction {
+
+ /** The user submitted the form (accepted). */
+ ACCEPT("accept"),
+
+ /** The user explicitly rejected the request. */
+ DECLINE("decline"),
+
+ /** The user dismissed the dialog without responding. */
+ CANCEL("cancel");
+
+ private final String value;
+
+ ElicitationResultAction(String value) {
+ this.value = value;
+ }
+
+ /** Returns the wire-format string value. @return the string value */
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/json/ElicitationSchema.java b/src/main/java/com/github/copilot/sdk/json/ElicitationSchema.java
new file mode 100644
index 000000000..c3d548775
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/json/ElicitationSchema.java
@@ -0,0 +1,92 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.json;
+
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * JSON Schema describing the form fields to present for an elicitation dialog.
+ *
+ * @since 1.0.0
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class ElicitationSchema {
+
+ @JsonProperty("type")
+ private String type = "object";
+
+ @JsonProperty("properties")
+ private Map properties;
+
+ @JsonProperty("required")
+ private List required;
+
+ /**
+ * Gets the schema type indicator (always {@code "object"}).
+ *
+ * @return the type
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Sets the schema type indicator.
+ *
+ * @param type
+ * the type (typically {@code "object"})
+ * @return this instance for method chaining
+ */
+ public ElicitationSchema setType(String type) {
+ this.type = type;
+ return this;
+ }
+
+ /**
+ * Gets the form field definitions, keyed by field name.
+ *
+ * @return the properties map
+ */
+ public Map getProperties() {
+ return properties;
+ }
+
+ /**
+ * Sets the form field definitions, keyed by field name.
+ *
+ * @param properties
+ * the properties map
+ * @return this instance for method chaining
+ */
+ public ElicitationSchema setProperties(Map properties) {
+ this.properties = properties;
+ return this;
+ }
+
+ /**
+ * Gets the list of required field names.
+ *
+ * @return the required field names, or {@code null}
+ */
+ public List getRequired() {
+ return required;
+ }
+
+ /**
+ * Sets the list of required field names.
+ *
+ * @param required
+ * the required field names
+ * @return this instance for method chaining
+ */
+ public ElicitationSchema setRequired(List required) {
+ this.required = required;
+ return this;
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/json/GetSessionMetadataResponse.java b/src/main/java/com/github/copilot/sdk/json/GetSessionMetadataResponse.java
new file mode 100644
index 000000000..eeceb4177
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/json/GetSessionMetadataResponse.java
@@ -0,0 +1,19 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.json;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Internal response object from getting session metadata by ID.
+ *
+ * @param session
+ * the session metadata, or {@code null} if not found
+ * @since 1.0.0
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public record GetSessionMetadataResponse(@JsonProperty("session") SessionMetadata session) {
+}
diff --git a/src/main/java/com/github/copilot/sdk/json/InputOptions.java b/src/main/java/com/github/copilot/sdk/json/InputOptions.java
new file mode 100644
index 000000000..9b0b6c8dd
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/json/InputOptions.java
@@ -0,0 +1,108 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.json;
+
+/**
+ * Options for the {@link SessionUiApi#input(String, InputOptions)} convenience
+ * method.
+ *
+ * @since 1.0.0
+ */
+public class InputOptions {
+
+ private String title;
+ private String description;
+ private Integer minLength;
+ private Integer maxLength;
+ private String format;
+ private String defaultValue;
+
+ /** Gets the title label for the input field. @return the title */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * Sets the title label for the input field. @param title the title @return this
+ */
+ public InputOptions setTitle(String title) {
+ this.title = title;
+ return this;
+ }
+
+ /** Gets the descriptive text shown below the field. @return the description */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Sets the descriptive text shown below the field. @param description the
+ * description @return this
+ */
+ public InputOptions setDescription(String description) {
+ this.description = description;
+ return this;
+ }
+
+ /** Gets the minimum character length. @return the min length */
+ public Integer getMinLength() {
+ return minLength;
+ }
+
+ /**
+ * Sets the minimum character length. @param minLength the min length @return
+ * this
+ */
+ public InputOptions setMinLength(Integer minLength) {
+ this.minLength = minLength;
+ return this;
+ }
+
+ /** Gets the maximum character length. @return the max length */
+ public Integer getMaxLength() {
+ return maxLength;
+ }
+
+ /**
+ * Sets the maximum character length. @param maxLength the max length @return
+ * this
+ */
+ public InputOptions setMaxLength(Integer maxLength) {
+ this.maxLength = maxLength;
+ return this;
+ }
+
+ /**
+ * Gets the semantic format hint (e.g., {@code "email"}, {@code "uri"},
+ * {@code "date"}, {@code "date-time"}).
+ *
+ * @return the format hint
+ */
+ public String getFormat() {
+ return format;
+ }
+
+ /** Sets the semantic format hint. @param format the format @return this */
+ public InputOptions setFormat(String format) {
+ this.format = format;
+ return this;
+ }
+
+ /**
+ * Gets the default value pre-populated in the field. @return the default value
+ */
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
+ /**
+ * Sets the default value pre-populated in the field. @param defaultValue the
+ * default value @return this
+ */
+ public InputOptions setDefaultValue(String defaultValue) {
+ this.defaultValue = defaultValue;
+ return this;
+ }
+}
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 eab3c789c..139f5238b 100644
--- a/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java
+++ b/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java
@@ -58,6 +58,8 @@ public class ResumeSessionConfig {
private List disabledSkills;
private InfiniteSessionConfig infiniteSessions;
private Consumer onEvent;
+ private List commands;
+ private ElicitationHandler onElicitationRequest;
/**
* Gets the AI model to use.
@@ -555,6 +557,56 @@ public ResumeSessionConfig setOnEvent(Consumer onEvent) {
return this;
}
+ /**
+ * Gets the slash commands registered for this session.
+ *
+ * @return the list of command definitions, or {@code null}
+ */
+ public List getCommands() {
+ return commands == null ? null : Collections.unmodifiableList(commands);
+ }
+
+ /**
+ * Sets slash commands registered for this session.
+ *
+ * When the CLI has a TUI, each command appears as {@code /name} for the user to
+ * invoke. The handler is called when the user executes the command.
+ *
+ * @param commands
+ * the list of command definitions
+ * @return this config for method chaining
+ * @see CommandDefinition
+ */
+ public ResumeSessionConfig setCommands(List commands) {
+ this.commands = commands;
+ return this;
+ }
+
+ /**
+ * Gets the elicitation request handler.
+ *
+ * @return the elicitation handler, or {@code null}
+ */
+ public ElicitationHandler getOnElicitationRequest() {
+ return onElicitationRequest;
+ }
+
+ /**
+ * Sets a handler for elicitation requests from the server or MCP tools.
+ *
+ * When provided, the server will route elicitation requests to this handler and
+ * report elicitation as a supported capability.
+ *
+ * @param onElicitationRequest
+ * the elicitation handler
+ * @return this config for method chaining
+ * @see ElicitationHandler
+ */
+ public ResumeSessionConfig setOnElicitationRequest(ElicitationHandler onElicitationRequest) {
+ this.onElicitationRequest = onElicitationRequest;
+ return this;
+ }
+
/**
* Creates a shallow clone of this {@code ResumeSessionConfig} instance.
*
@@ -591,6 +643,8 @@ public ResumeSessionConfig clone() {
copy.disabledSkills = this.disabledSkills != null ? new ArrayList<>(this.disabledSkills) : null;
copy.infiniteSessions = this.infiniteSessions;
copy.onEvent = this.onEvent;
+ copy.commands = this.commands != null ? new ArrayList<>(this.commands) : null;
+ copy.onElicitationRequest = this.onElicitationRequest;
return copy;
}
}
diff --git a/src/main/java/com/github/copilot/sdk/json/ResumeSessionRequest.java b/src/main/java/com/github/copilot/sdk/json/ResumeSessionRequest.java
index 31d88399a..7be9a6281 100644
--- a/src/main/java/com/github/copilot/sdk/json/ResumeSessionRequest.java
+++ b/src/main/java/com/github/copilot/sdk/json/ResumeSessionRequest.java
@@ -95,6 +95,12 @@ public final class ResumeSessionRequest {
@JsonProperty("infiniteSessions")
private InfiniteSessionConfig infiniteSessions;
+ @JsonProperty("commands")
+ private List commands;
+
+ @JsonProperty("requestElicitation")
+ private Boolean requestElicitation;
+
/** Gets the session ID. @return the session ID */
public String getSessionId() {
return sessionId;
@@ -332,4 +338,24 @@ public InfiniteSessionConfig getInfiniteSessions() {
public void setInfiniteSessions(InfiniteSessionConfig infiniteSessions) {
this.infiniteSessions = infiniteSessions;
}
+
+ /** Gets the commands wire definitions. @return the commands */
+ public List getCommands() {
+ return commands == null ? null : Collections.unmodifiableList(commands);
+ }
+
+ /** Sets the commands wire definitions. @param commands the commands */
+ public void setCommands(List commands) {
+ this.commands = commands;
+ }
+
+ /** Gets the requestElicitation flag. @return the flag */
+ public Boolean getRequestElicitation() {
+ return requestElicitation;
+ }
+
+ /** Sets the requestElicitation flag. @param requestElicitation the flag */
+ public void setRequestElicitation(Boolean requestElicitation) {
+ this.requestElicitation = requestElicitation;
+ }
}
diff --git a/src/main/java/com/github/copilot/sdk/json/ResumeSessionResponse.java b/src/main/java/com/github/copilot/sdk/json/ResumeSessionResponse.java
index 654c1486c..8349c5d30 100644
--- a/src/main/java/com/github/copilot/sdk/json/ResumeSessionResponse.java
+++ b/src/main/java/com/github/copilot/sdk/json/ResumeSessionResponse.java
@@ -11,9 +11,12 @@
* @param workspacePath
* the workspace path, or {@code null} if infinite sessions are
* disabled
+ * @param capabilities
+ * the capabilities reported by the host, or {@code null}
* @since 1.0.0
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public record ResumeSessionResponse(@JsonProperty("sessionId") String sessionId,
- @JsonProperty("workspacePath") String workspacePath) {
+ @JsonProperty("workspacePath") String workspacePath,
+ @JsonProperty("capabilities") SessionCapabilities capabilities) {
}
diff --git a/src/main/java/com/github/copilot/sdk/json/SessionCapabilities.java b/src/main/java/com/github/copilot/sdk/json/SessionCapabilities.java
new file mode 100644
index 000000000..4eb4fc025
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/json/SessionCapabilities.java
@@ -0,0 +1,39 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.json;
+
+/**
+ * Represents the capabilities reported by the host for a session.
+ *
+ * Capabilities are populated from the session create/resume response and
+ * updated in real time via {@code capabilities.changed} events.
+ *
+ * @since 1.0.0
+ */
+public class SessionCapabilities {
+
+ private SessionUiCapabilities ui;
+
+ /**
+ * Gets the UI-related capabilities.
+ *
+ * @return the UI capabilities, or {@code null} if not reported
+ */
+ public SessionUiCapabilities getUi() {
+ return ui;
+ }
+
+ /**
+ * Sets the UI-related capabilities.
+ *
+ * @param ui
+ * the UI capabilities
+ * @return this instance for method chaining
+ */
+ public SessionCapabilities setUi(SessionUiCapabilities ui) {
+ this.ui = ui;
+ return this;
+ }
+}
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 76c15660d..5dcd39788 100644
--- a/src/main/java/com/github/copilot/sdk/json/SessionConfig.java
+++ b/src/main/java/com/github/copilot/sdk/json/SessionConfig.java
@@ -58,6 +58,8 @@ public class SessionConfig {
private List disabledSkills;
private String configDir;
private Consumer onEvent;
+ private List commands;
+ private ElicitationHandler onElicitationRequest;
/**
* Gets the custom session ID.
@@ -595,6 +597,56 @@ public SessionConfig setOnEvent(Consumer onEvent) {
return this;
}
+ /**
+ * Gets the slash commands registered for this session.
+ *
+ * @return the list of command definitions, or {@code null}
+ */
+ public List getCommands() {
+ return commands == null ? null : Collections.unmodifiableList(commands);
+ }
+
+ /**
+ * Sets slash commands registered for this session.
+ *
+ * When the CLI has a TUI, each command appears as {@code /name} for the user to
+ * invoke. The handler is called when the user executes the command.
+ *
+ * @param commands
+ * the list of command definitions
+ * @return this config instance for method chaining
+ * @see CommandDefinition
+ */
+ public SessionConfig setCommands(List commands) {
+ this.commands = commands;
+ return this;
+ }
+
+ /**
+ * Gets the elicitation request handler.
+ *
+ * @return the elicitation handler, or {@code null}
+ */
+ public ElicitationHandler getOnElicitationRequest() {
+ return onElicitationRequest;
+ }
+
+ /**
+ * Sets a handler for elicitation requests from the server or MCP tools.
+ *
+ * When provided, the server will route elicitation requests to this handler and
+ * report elicitation as a supported capability.
+ *
+ * @param onElicitationRequest
+ * the elicitation handler
+ * @return this config instance for method chaining
+ * @see ElicitationHandler
+ */
+ public SessionConfig setOnElicitationRequest(ElicitationHandler onElicitationRequest) {
+ this.onElicitationRequest = onElicitationRequest;
+ return this;
+ }
+
/**
* Creates a shallow clone of this {@code SessionConfig} instance.
*
@@ -631,6 +683,8 @@ public SessionConfig clone() {
copy.disabledSkills = this.disabledSkills != null ? new ArrayList<>(this.disabledSkills) : null;
copy.configDir = this.configDir;
copy.onEvent = this.onEvent;
+ copy.commands = this.commands != null ? new ArrayList<>(this.commands) : null;
+ copy.onElicitationRequest = this.onElicitationRequest;
return copy;
}
}
diff --git a/src/main/java/com/github/copilot/sdk/json/SessionUiApi.java b/src/main/java/com/github/copilot/sdk/json/SessionUiApi.java
new file mode 100644
index 000000000..f0a43f261
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/json/SessionUiApi.java
@@ -0,0 +1,86 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.json;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Provides UI methods for eliciting information from the user during a session.
+ *
+ * All methods on this interface throw {@link IllegalStateException} if the host
+ * does not report elicitation support via
+ * {@link com.github.copilot.sdk.CopilotSession#getCapabilities()}. Check
+ * {@code session.getCapabilities().getUi() != null &&
+ * Boolean.TRUE.equals(session.getCapabilities().getUi().getElicitation())}
+ * before calling.
+ *
+ *
Example Usage
+ *
+ *
{@code
+ * var caps = session.getCapabilities();
+ * if (caps.getUi() != null && Boolean.TRUE.equals(caps.getUi().getElicitation())) {
+ * boolean confirmed = session.getUi().confirm("Are you sure?").get();
+ * }
+ * }
+ *
+ * @see com.github.copilot.sdk.CopilotSession#getUi()
+ * @since 1.0.0
+ */
+public interface SessionUiApi {
+
+ /**
+ * Shows a generic elicitation dialog with a custom schema.
+ *
+ * @param params
+ * the elicitation parameters including message and schema
+ * @return a future that resolves with the {@link ElicitationResult}
+ * @throws IllegalStateException
+ * if the host does not support elicitation
+ */
+ CompletableFuture elicitation(ElicitationParams params);
+
+ /**
+ * Shows a confirmation dialog and returns the user's boolean answer.
+ *
+ * Returns {@code false} if the user declines or cancels.
+ *
+ * @param message
+ * the message to display
+ * @return a future that resolves to {@code true} if the user confirmed
+ * @throws IllegalStateException
+ * if the host does not support elicitation
+ */
+ CompletableFuture confirm(String message);
+
+ /**
+ * Shows a selection dialog with the given options.
+ *
+ * Returns the selected value, or {@code null} if the user declines/cancels.
+ *
+ * @param message
+ * the message to display
+ * @param options
+ * the options to present
+ * @return a future that resolves to the selected string, or {@code null}
+ * @throws IllegalStateException
+ * if the host does not support elicitation
+ */
+ CompletableFuture select(String message, String[] options);
+
+ /**
+ * Shows a text input dialog.
+ *
+ * Returns the entered text, or {@code null} if the user declines/cancels.
+ *
+ * @param message
+ * the message to display
+ * @param options
+ * optional input field options, or {@code null}
+ * @return a future that resolves to the entered string, or {@code null}
+ * @throws IllegalStateException
+ * if the host does not support elicitation
+ */
+ CompletableFuture input(String message, InputOptions options);
+}
diff --git a/src/main/java/com/github/copilot/sdk/json/SessionUiCapabilities.java b/src/main/java/com/github/copilot/sdk/json/SessionUiCapabilities.java
new file mode 100644
index 000000000..9b8e0b587
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/json/SessionUiCapabilities.java
@@ -0,0 +1,37 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.json;
+
+/**
+ * UI-specific capability flags for a session.
+ *
+ * @since 1.0.0
+ */
+public class SessionUiCapabilities {
+
+ private Boolean elicitation;
+
+ /**
+ * Returns whether the host supports interactive elicitation dialogs.
+ *
+ * @return {@code true} if elicitation is supported, {@code false} or
+ * {@code null} otherwise
+ */
+ public Boolean getElicitation() {
+ return elicitation;
+ }
+
+ /**
+ * Sets whether the host supports interactive elicitation dialogs.
+ *
+ * @param elicitation
+ * {@code true} if elicitation is supported
+ * @return this instance for method chaining
+ */
+ public SessionUiCapabilities setElicitation(Boolean elicitation) {
+ this.elicitation = elicitation;
+ return this;
+ }
+}
diff --git a/src/site/markdown/advanced.md b/src/site/markdown/advanced.md
index bc9302840..5ae5c8f94 100644
--- a/src/site/markdown/advanced.md
+++ b/src/site/markdown/advanced.md
@@ -47,6 +47,13 @@ This guide covers advanced scenarios for extending and customizing your Copilot
- [Custom Event Error Handler](#Custom_Event_Error_Handler)
- [Event Error Policy](#Event_Error_Policy)
- [OpenTelemetry](#OpenTelemetry)
+- [Slash Commands](#Slash_Commands)
+ - [Registering Commands](#Registering_Commands)
+- [Elicitation (UI Dialogs)](#Elicitation_UI_Dialogs)
+ - [Incoming Elicitation Handler](#Incoming_Elicitation_Handler)
+ - [Session Capabilities](#Session_Capabilities)
+ - [Outgoing Elicitation via session.getUi()](#Outgoing_Elicitation_via_session.getUi)
+- [Getting Session Metadata by ID](#Getting_Session_Metadata_by_ID)
---
@@ -1093,6 +1100,143 @@ See [TelemetryConfig](apidocs/com/github/copilot/sdk/json/TelemetryConfig.html)
---
+## Slash Commands
+
+Register custom slash commands that users can invoke from the CLI TUI with `/commandname`.
+
+### Registering Commands
+
+```java
+var config = new SessionConfig()
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+ .setCommands(List.of(
+ new CommandDefinition()
+ .setName("deploy")
+ .setDescription("Deploy the current branch")
+ .setHandler(context -> {
+ System.out.println("Deploying with args: " + context.getArgs());
+ // perform deployment ...
+ return CompletableFuture.completedFuture(null);
+ }),
+ new CommandDefinition()
+ .setName("rollback")
+ .setDescription("Roll back the last deployment")
+ .setHandler(context -> {
+ // perform rollback ...
+ return CompletableFuture.completedFuture(null);
+ })
+ ));
+
+try (CopilotClient client = new CopilotClient()) {
+ client.start().get();
+ var session = client.createSession(config).get();
+ // Users can now type /deploy or /rollback in the TUI
+}
+```
+
+Each `CommandDefinition` requires a `name` (without the leading `/`), an optional `description` shown in the TUI's command completion UI, and a `CommandHandler` that is invoked when the user executes the command.
+
+The `CommandContext` passed to the handler provides:
+- `getSessionId()` — the ID of the session where the command was invoked
+- `getCommand()` — the full command text (e.g., `/deploy production`)
+- `getCommandName()` — command name without the leading `/` (e.g., `deploy`)
+- `getArgs()` — the argument string after the command name (e.g., `production`)
+
+---
+
+## Elicitation (UI Dialogs)
+
+Elicitation allows your application to present structured UI dialogs to the user. There are two directions:
+
+1. **Incoming** — The server or an MCP tool requests input from the user via your `onElicitationRequest` handler.
+2. **Outgoing** — Your session-side code proactively requests input via `session.getUi()`.
+
+### Incoming Elicitation Handler
+
+Register a handler to receive elicitation requests from the server:
+
+```java
+var config = new SessionConfig()
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+ .setOnElicitationRequest(context -> {
+ System.out.println("Elicitation request: " + context.getMessage());
+ // Show the form to the user ...
+ var content = Map.of("confirmed", true);
+ return CompletableFuture.completedFuture(
+ new ElicitationResult()
+ .setAction(ElicitationResultAction.ACCEPT)
+ .setContent(content)
+ );
+ });
+```
+
+When `onElicitationRequest` is set, the SDK reports elicitation as a supported capability and the server will route elicitation requests to your handler.
+
+### Session Capabilities
+
+After `createSession` or `resumeSession`, check `session.getCapabilities()` to see what the host supports:
+
+```java
+var session = client.createSession(config).get();
+
+var caps = session.getCapabilities();
+if (caps.getUi() != null && Boolean.TRUE.equals(caps.getUi().getElicitation())) {
+ System.out.println("Elicitation is supported");
+}
+```
+
+Capabilities are updated in real time when a `capabilities.changed` event is received.
+
+### Outgoing Elicitation via `session.getUi()`
+
+If the host reports elicitation support, you can call the convenience methods on `session.getUi()`:
+
+```java
+var ui = session.getUi();
+
+// Boolean confirmation
+boolean confirmed = ui.confirm("Are you sure you want to proceed?").get();
+
+// Selection from options
+String choice = ui.select("Choose an environment", new String[]{"dev", "staging", "prod"}).get();
+
+// Text input
+String value = ui.input("Enter your name", null).get();
+
+// Custom schema
+var result = ui.elicitation(new ElicitationParams()
+ .setMessage("Enter deployment details")
+ .setRequestedSchema(new ElicitationSchema()
+ .setProperties(Map.of(
+ "branch", Map.of("type", "string"),
+ "environment", Map.of("type", "string", "enum", List.of("dev", "staging", "prod"))
+ ))
+ .setRequired(List.of("branch", "environment"))
+ )).get();
+```
+
+All `getUi()` methods throw `IllegalStateException` if the host does not support elicitation. Always check capabilities first.
+
+---
+
+## Getting Session Metadata by ID
+
+Retrieve metadata for a specific session without listing all sessions:
+
+```java
+SessionMetadata metadata = client.getSessionMetadata("session-123").get();
+if (metadata != null) {
+ System.out.println("Session: " + metadata.getSessionId());
+ System.out.println("Started: " + metadata.getStartTime());
+} else {
+ System.out.println("Session not found");
+}
+```
+
+This is more efficient than `listSessions()` when you already know the session ID, as it performs a direct O(1) lookup instead of scanning all sessions.
+
+---
+
## Next Steps
- 📖 **[Documentation](documentation.html)** - Core concepts, events, streaming, models, tool filtering, reasoning effort
diff --git a/src/site/markdown/cookbook/error-handling.md b/src/site/markdown/cookbook/error-handling.md
index 5ee5ef2ca..4240dc1ff 100644
--- a/src/site/markdown/cookbook/error-handling.md
+++ b/src/site/markdown/cookbook/error-handling.md
@@ -30,7 +30,7 @@ jbang BasicErrorHandling.java
**Code:**
```java
-//DEPS com.github:copilot-sdk-java:0.2.1-java.1
+//DEPS com.github:copilot-sdk-java:0.2.2-java.1
import com.github.copilot.sdk.CopilotClient;
import com.github.copilot.sdk.events.AssistantMessageEvent;
import com.github.copilot.sdk.json.MessageOptions;
@@ -64,7 +64,7 @@ public class BasicErrorHandling {
## Handling specific error types
```java
-//DEPS com.github:copilot-sdk-java:0.2.1-java.1
+//DEPS com.github:copilot-sdk-java:0.2.2-java.1
import com.github.copilot.sdk.CopilotClient;
import java.util.concurrent.ExecutionException;
@@ -99,7 +99,7 @@ public class SpecificErrorHandling {
## Timeout handling
```java
-//DEPS com.github:copilot-sdk-java:0.2.1-java.1
+//DEPS com.github:copilot-sdk-java:0.2.2-java.1
import com.github.copilot.sdk.CopilotSession;
import com.github.copilot.sdk.events.AssistantMessageEvent;
import com.github.copilot.sdk.json.MessageOptions;
@@ -130,7 +130,7 @@ public class TimeoutHandling {
## Aborting a request
```java
-//DEPS com.github:copilot-sdk-java:0.2.1-java.1
+//DEPS com.github:copilot-sdk-java:0.2.2-java.1
import com.github.copilot.sdk.CopilotSession;
import com.github.copilot.sdk.json.MessageOptions;
import java.util.concurrent.Executors;
@@ -162,7 +162,7 @@ public class AbortRequest {
## Graceful shutdown
```java
-//DEPS com.github:copilot-sdk-java:0.2.1-java.1
+//DEPS com.github:copilot-sdk-java:0.2.2-java.1
import com.github.copilot.sdk.CopilotClient;
public class GracefulShutdown {
@@ -192,7 +192,7 @@ public class GracefulShutdown {
## Try-with-resources pattern
```java
-//DEPS com.github:copilot-sdk-java:0.2.1-java.1
+//DEPS com.github:copilot-sdk-java:0.2.2-java.1
import com.github.copilot.sdk.CopilotClient;
import com.github.copilot.sdk.events.AssistantMessageEvent;
import com.github.copilot.sdk.json.MessageOptions;
@@ -224,7 +224,7 @@ public class TryWithResources {
## Handling tool errors
```java
-//DEPS com.github:copilot-sdk-java:0.2.1-java.1
+//DEPS com.github:copilot-sdk-java:0.2.2-java.1
import com.github.copilot.sdk.CopilotClient;
import com.github.copilot.sdk.events.AssistantMessageEvent;
import com.github.copilot.sdk.json.MessageOptions;
diff --git a/src/site/markdown/cookbook/managing-local-files.md b/src/site/markdown/cookbook/managing-local-files.md
index aa9ba23bc..9535772b2 100644
--- a/src/site/markdown/cookbook/managing-local-files.md
+++ b/src/site/markdown/cookbook/managing-local-files.md
@@ -34,7 +34,7 @@ jbang ManagingLocalFiles.java
**Code:**
```java
-//DEPS com.github:copilot-sdk-java:0.2.1-java.1
+//DEPS com.github:copilot-sdk-java:0.2.2-java.1
import com.github.copilot.sdk.CopilotClient;
import com.github.copilot.sdk.events.AssistantMessageEvent;
import com.github.copilot.sdk.events.SessionIdleEvent;
@@ -161,7 +161,7 @@ session.send(new MessageOptions().setPrompt(prompt));
## Interactive file organization
```java
-//DEPS com.github:copilot-sdk-java:0.2.1-java.1
+//DEPS com.github:copilot-sdk-java:0.2.2-java.1
import java.io.BufferedReader;
import java.io.InputStreamReader;
diff --git a/src/site/markdown/cookbook/multiple-sessions.md b/src/site/markdown/cookbook/multiple-sessions.md
index fe5c2f0d9..776b6db6d 100644
--- a/src/site/markdown/cookbook/multiple-sessions.md
+++ b/src/site/markdown/cookbook/multiple-sessions.md
@@ -30,7 +30,7 @@ jbang MultipleSessions.java
**Code:**
```java
-//DEPS com.github:copilot-sdk-java:0.2.1-java.1
+//DEPS com.github:copilot-sdk-java:0.2.2-java.1
import com.github.copilot.sdk.CopilotClient;
import com.github.copilot.sdk.events.AssistantMessageEvent;
import com.github.copilot.sdk.json.MessageOptions;
@@ -123,7 +123,7 @@ try {
## Managing session lifecycle with CompletableFuture
```java
-//DEPS com.github:copilot-sdk-java:0.2.1-java.1
+//DEPS com.github:copilot-sdk-java:0.2.2-java.1
import java.util.concurrent.CompletableFuture;
import java.util.List;
diff --git a/src/site/markdown/cookbook/persisting-sessions.md b/src/site/markdown/cookbook/persisting-sessions.md
index e3fd11b13..e653b8a6a 100644
--- a/src/site/markdown/cookbook/persisting-sessions.md
+++ b/src/site/markdown/cookbook/persisting-sessions.md
@@ -30,7 +30,7 @@ jbang PersistingSessions.java
**Code:**
```java
-//DEPS com.github:copilot-sdk-java:0.2.1-java.1
+//DEPS com.github:copilot-sdk-java:0.2.2-java.1
import com.github.copilot.sdk.CopilotClient;
import com.github.copilot.sdk.events.AssistantMessageEvent;
import com.github.copilot.sdk.json.MessageOptions;
@@ -127,7 +127,7 @@ public class DeleteSession {
## Getting session history
```java
-//DEPS com.github:copilot-sdk-java:0.2.1-java.1
+//DEPS com.github:copilot-sdk-java:0.2.2-java.1
import com.github.copilot.sdk.CopilotClient;
import com.github.copilot.sdk.events.AssistantMessageEvent;
import com.github.copilot.sdk.events.UserMessageEvent;
@@ -162,7 +162,7 @@ public class SessionHistory {
## Complete example with session management
```java
-//DEPS com.github:copilot-sdk-java:0.2.1-java.1
+//DEPS com.github:copilot-sdk-java:0.2.2-java.1
import java.util.Scanner;
public class SessionManager {
diff --git a/src/site/markdown/cookbook/pr-visualization.md b/src/site/markdown/cookbook/pr-visualization.md
index dbd240a40..ad2939842 100644
--- a/src/site/markdown/cookbook/pr-visualization.md
+++ b/src/site/markdown/cookbook/pr-visualization.md
@@ -34,7 +34,7 @@ jbang PRVisualization.java github/copilot-sdk
## Full example: PRVisualization.java
```java
-//DEPS com.github:copilot-sdk-java:0.2.1-java.1
+//DEPS com.github:copilot-sdk-java:0.2.2-java.1
import com.github.copilot.sdk.CopilotClient;
import com.github.copilot.sdk.events.AssistantMessageEvent;
import com.github.copilot.sdk.events.ToolExecutionStartEvent;
diff --git a/src/site/markdown/documentation.md b/src/site/markdown/documentation.md
index 8a9f919ac..a96f66698 100644
--- a/src/site/markdown/documentation.md
+++ b/src/site/markdown/documentation.md
@@ -245,6 +245,19 @@ The SDK supports event types organized by category. All events extend `AbstractS
|-------|-------------|-------------|
| `CommandQueuedEvent` | `command.queued` | A command was queued for execution |
| `CommandCompletedEvent` | `command.completed` | A queued command completed |
+| `CommandExecuteEvent` | `command.execute` | A registered slash command was dispatched for execution |
+
+### Elicitation Events
+
+| Event | Type String | Description |
+|-------|-------------|-------------|
+| `ElicitationRequestedEvent` | `elicitation.requested` | An elicitation (UI dialog) request was received |
+
+### Capability Events
+
+| Event | Type String | Description |
+|-------|-------------|-------------|
+| `CapabilitiesChangedEvent` | `capabilities.changed` | Session capabilities were updated |
### Plan Mode Events
@@ -633,6 +646,8 @@ When resuming a session, you can optionally reconfigure many settings. This is u
| `skillDirectories` | Directories to load skills from |
| `disabledSkills` | Skills to disable |
| `infiniteSessions` | Configure infinite session behavior |
+| `commands` | Slash command definitions for the resumed session |
+| `onElicitationRequest` | Handler for incoming elicitation requests |
| `disableResume` | When `true`, resumes without emitting a `session.resume` event |
| `onEvent` | Event handler registered before session resumption |
@@ -691,6 +706,8 @@ Complete list of all `SessionConfig` options for `createSession()`:
| `skillDirectories` | List<String> | Directories to load skills from | [Skills](advanced.html#Skills_Configuration) |
| `disabledSkills` | List<String> | Skills to disable by name | [Skills](advanced.html#Skills_Configuration) |
| `configDir` | String | Custom configuration directory | [Config Dir](advanced.html#Custom_Configuration_Directory) |
+| `commands` | List<CommandDefinition> | Slash command definitions | [Slash Commands](advanced.html#Slash_Commands) |
+| `onElicitationRequest` | ElicitationHandler | Handler for incoming elicitation requests | [Elicitation](advanced.html#Elicitation_UI_Dialogs) |
| `onEvent` | Consumer<AbstractSessionEvent> | Event handler registered before session creation | [Early Event Registration](advanced.html#Early_Event_Registration) |
### Cloning SessionConfig
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
index 2f93c4ce9..60b96ce9d 100644
--- a/src/site/markdown/index.md
+++ b/src/site/markdown/index.md
@@ -1,6 +1,6 @@
# GitHub Copilot SDK for Java
-> ⚠️ **Disclaimer:** This is the official Java SDK for GitHub Copilot. This repository treats the official .NET and nodejs SDKs for GitHub Copilot as reference implementations. These SDKS are all officially supported as GitHub open source projects. The Java implementation follows the backward compatibility guarantees offered by the reference implementations. As such this implementation may introduce breaking changes, according to the policy declared by the reference implementations. Use at your own risk.
+> ℹ️ **Public Preview:** This is the official Java SDK for GitHub Copilot. This repository treats the official .NET and Node.js SDKs for GitHub Copilot as reference implementations. These SDKs are all officially supported as GitHub open source projects. The Java implementation follows the backward compatibility guarantees offered by the reference implementations. While in public preview, minor breaking changes may still occur between releases.
Welcome to the documentation for the **GitHub Copilot SDK for Java** — a Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build AI-powered applications and agentic workflows.
@@ -9,7 +9,7 @@ Welcome to the documentation for the **GitHub Copilot SDK for Java** — a Java
### Requirements
- Java 17 or later
-- GitHub Copilot CLI 0.0.411-1 or later installed and in PATH (or provide custom `cliPath`)
+- GitHub Copilot CLI 1.0.17 or later installed and in PATH (or provide custom `cliPath`)
### Installation
diff --git a/src/site/site.xml b/src/site/site.xml
index f89ebe076..d012c0335 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -59,6 +59,9 @@
+
+
+
diff --git a/src/test/java/com/github/copilot/sdk/AgentInfoTest.java b/src/test/java/com/github/copilot/sdk/AgentInfoTest.java
new file mode 100644
index 000000000..0893773e7
--- /dev/null
+++ b/src/test/java/com/github/copilot/sdk/AgentInfoTest.java
@@ -0,0 +1,64 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+import com.github.copilot.sdk.json.AgentInfo;
+
+/**
+ * Unit tests for {@link AgentInfo} getters, setters, and fluent chaining.
+ */
+class AgentInfoTest {
+
+ @Test
+ void defaultValuesAreNull() {
+ var agent = new AgentInfo();
+ assertNull(agent.getName());
+ assertNull(agent.getDisplayName());
+ assertNull(agent.getDescription());
+ }
+
+ @Test
+ void nameGetterSetter() {
+ var agent = new AgentInfo();
+ agent.setName("coder");
+ assertEquals("coder", agent.getName());
+ }
+
+ @Test
+ void displayNameGetterSetter() {
+ var agent = new AgentInfo();
+ agent.setDisplayName("Code Assistant");
+ assertEquals("Code Assistant", agent.getDisplayName());
+ }
+
+ @Test
+ void descriptionGetterSetter() {
+ var agent = new AgentInfo();
+ agent.setDescription("Helps with coding tasks");
+ assertEquals("Helps with coding tasks", agent.getDescription());
+ }
+
+ @Test
+ void fluentChainingReturnsThis() {
+ var agent = new AgentInfo().setName("coder").setDisplayName("Code Assistant")
+ .setDescription("Helps with coding tasks");
+
+ assertEquals("coder", agent.getName());
+ assertEquals("Code Assistant", agent.getDisplayName());
+ assertEquals("Helps with coding tasks", agent.getDescription());
+ }
+
+ @Test
+ void fluentChainingReturnsSameInstance() {
+ var agent = new AgentInfo();
+ assertSame(agent, agent.setName("test"));
+ assertSame(agent, agent.setDisplayName("Test"));
+ assertSame(agent, agent.setDescription("A test agent"));
+ }
+}
diff --git a/src/test/java/com/github/copilot/sdk/CapiProxy.java b/src/test/java/com/github/copilot/sdk/CapiProxy.java
index 1a7df2d6c..bcd064d94 100644
--- a/src/test/java/com/github/copilot/sdk/CapiProxy.java
+++ b/src/test/java/com/github/copilot/sdk/CapiProxy.java
@@ -89,7 +89,11 @@ public String start() throws IOException, InterruptedException {
}
// Start the harness server using npx tsx
- var pb = new ProcessBuilder("npx", "tsx", "server.ts");
+ // On Windows, npx is installed as npx.cmd which requires cmd /c to launch
+ boolean isWindows = System.getProperty("os.name").toLowerCase().contains("win");
+ var pb = isWindows
+ ? new ProcessBuilder("cmd", "/c", "npx", "tsx", "server.ts")
+ : new ProcessBuilder("npx", "tsx", "server.ts");
pb.directory(harnessDir.toFile());
pb.redirectErrorStream(false);
diff --git a/src/test/java/com/github/copilot/sdk/CliServerManagerTest.java b/src/test/java/com/github/copilot/sdk/CliServerManagerTest.java
index f17201583..e556839cc 100644
--- a/src/test/java/com/github/copilot/sdk/CliServerManagerTest.java
+++ b/src/test/java/com/github/copilot/sdk/CliServerManagerTest.java
@@ -13,6 +13,7 @@
import org.junit.jupiter.api.Test;
import com.github.copilot.sdk.json.CopilotClientOptions;
+import com.github.copilot.sdk.json.TelemetryConfig;
/**
* Unit tests for {@link CliServerManager} covering parseCliUrl,
@@ -69,13 +70,18 @@ void connectToServerTcpMode() throws Exception {
}
}
+ private static Process startBlockingProcess() throws IOException {
+ boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows");
+ return (isWindows ? new ProcessBuilder("cmd", "/c", "more") : new ProcessBuilder("cat")).start();
+ }
+
@Test
void connectToServerStdioMode() throws Exception {
var options = new CopilotClientOptions();
var manager = new CliServerManager(options);
// Create a dummy process for stdio mode
- Process process = new ProcessBuilder("cat").start();
+ Process process = startBlockingProcess();
try {
JsonRpcClient client = manager.connectToServer(process, null, null);
assertNotNull(client);
@@ -125,6 +131,14 @@ void processInfoWithNullPort() {
// resolveCliCommand is private, so we test indirectly through startCliServer
// with specific cliPath values.
+ // On Windows, "/nonexistent/copilot" is not an absolute path (no drive letter),
+ // so resolveCliCommand wraps it with "cmd /c" and ProcessBuilder.start()
+ // succeeds
+ // (launching cmd.exe). Use a Windows-absolute path to ensure IOException.
+ private static final String NONEXISTENT_CLI = System.getProperty("os.name").toLowerCase().contains("win")
+ ? "C:\\nonexistent\\copilot"
+ : "/nonexistent/copilot";
+
@Test
void startCliServerWithJsFile() throws Exception {
// Using a .js file path causes resolveCliCommand to prepend "node"
@@ -146,8 +160,8 @@ void startCliServerWithJsFile() throws Exception {
@Test
void startCliServerWithCliArgs() throws Exception {
// Test that cliArgs are included in the command
- var options = new CopilotClientOptions().setCliPath("/nonexistent/copilot")
- .setCliArgs(new String[]{"--extra-flag"}).setUseStdio(true);
+ var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setCliArgs(new String[]{"--extra-flag"})
+ .setUseStdio(true);
var manager = new CliServerManager(options);
var ex = assertThrows(IOException.class, () -> manager.startCliServer());
@@ -157,7 +171,7 @@ void startCliServerWithCliArgs() throws Exception {
@Test
void startCliServerWithExplicitPort() throws Exception {
// Test the explicit port branch (useStdio=false, port > 0)
- var options = new CopilotClientOptions().setCliPath("/nonexistent/copilot").setUseStdio(false).setPort(9999);
+ var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setUseStdio(false).setPort(9999);
var manager = new CliServerManager(options);
var ex = assertThrows(IOException.class, () -> manager.startCliServer());
@@ -167,7 +181,7 @@ void startCliServerWithExplicitPort() throws Exception {
@Test
void startCliServerWithGitHubToken() throws Exception {
// Test the github token branch
- var options = new CopilotClientOptions().setCliPath("/nonexistent/copilot").setGitHubToken("ghp_test123")
+ var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setGitHubToken("ghp_test123")
.setUseStdio(true);
var manager = new CliServerManager(options);
@@ -178,7 +192,7 @@ void startCliServerWithGitHubToken() throws Exception {
@Test
void startCliServerWithUseLoggedInUserExplicit() throws Exception {
// Test the explicit useLoggedInUser=false branch (adds --no-auto-login)
- var options = new CopilotClientOptions().setCliPath("/nonexistent/copilot").setUseLoggedInUser(false)
+ var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setUseLoggedInUser(false)
.setUseStdio(true);
var manager = new CliServerManager(options);
@@ -189,7 +203,7 @@ void startCliServerWithUseLoggedInUserExplicit() throws Exception {
@Test
void startCliServerWithGitHubTokenAndNoExplicitUseLoggedInUser() throws Exception {
// When gitHubToken is set and useLoggedInUser is null, defaults to false
- var options = new CopilotClientOptions().setCliPath("/nonexistent/copilot").setGitHubToken("ghp_test123")
+ var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setGitHubToken("ghp_test123")
.setUseStdio(true);
var manager = new CliServerManager(options);
@@ -212,4 +226,28 @@ void startCliServerWithNullCliPath() throws Exception {
assertNotNull(e);
}
}
+
+ @Test
+ void startCliServerWithTelemetryAllOptions() throws Exception {
+ // The telemetry env vars are applied before ProcessBuilder.start()
+ // so even with a nonexistent CLI path, the telemetry code path is exercised
+ var telemetry = new TelemetryConfig().setOtlpEndpoint("http://localhost:4318").setFilePath("/tmp/telemetry.log")
+ .setExporterType("otlp-http").setSourceName("test-app").setCaptureContent(true);
+ var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setTelemetry(telemetry).setUseStdio(true);
+ var manager = new CliServerManager(options);
+
+ var ex = assertThrows(IOException.class, () -> manager.startCliServer());
+ assertNotNull(ex);
+ }
+
+ @Test
+ void startCliServerWithTelemetryCaptureContentFalse() throws Exception {
+ // Test the false branch of getCaptureContent()
+ var telemetry = new TelemetryConfig().setCaptureContent(false);
+ var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setTelemetry(telemetry).setUseStdio(true);
+ var manager = new CliServerManager(options);
+
+ var ex = assertThrows(IOException.class, () -> manager.startCliServer());
+ assertNotNull(ex);
+ }
}
diff --git a/src/test/java/com/github/copilot/sdk/CommandsTest.java b/src/test/java/com/github/copilot/sdk/CommandsTest.java
new file mode 100644
index 000000000..baf26b39b
--- /dev/null
+++ b/src/test/java/com/github/copilot/sdk/CommandsTest.java
@@ -0,0 +1,156 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+import org.junit.jupiter.api.Test;
+
+import com.github.copilot.sdk.json.CommandContext;
+import com.github.copilot.sdk.json.CommandDefinition;
+import com.github.copilot.sdk.json.CommandHandler;
+import com.github.copilot.sdk.json.CommandWireDefinition;
+import com.github.copilot.sdk.json.PermissionHandler;
+import com.github.copilot.sdk.json.ResumeSessionConfig;
+import com.github.copilot.sdk.json.SessionConfig;
+
+/**
+ * Unit tests for the Commands feature (CommandDefinition, CommandContext,
+ * SessionConfig.commands, ResumeSessionConfig.commands, and the wire
+ * representation).
+ *
+ *
+ * Ported from {@code CommandsTests.cs} in the upstream dotnet SDK.
+ *