diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml
new file mode 100644
index 000000000..9e5a9238f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug.yml
@@ -0,0 +1,41 @@
+name: Bug
+description: File a bug report
+title: "[BUG]: "
+labels: ["Type: Bug", "Status: Triage"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill out this bug report!
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: What happened?
+ description: What did you do? What happened? What did you expect to happen?
+ placeholder: Put your description of the bug here.
+ validations:
+ required: true
+ - type: textarea
+ id: versions
+ attributes:
+ label: Versions
+ description: What versions of the relevant software are you running?
+ placeholder: copilot-sdk-java v1.0.0, Java 17.0.10, Maven 3.9.6, Copilot CLI v1.0.0
+ validations:
+ required: true
+ - type: textarea
+ id: logs
+ attributes:
+ label: Relevant log output
+ description: |
+ Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
+ Please check your logs before submission to ensure sensitive information is redacted.
+ render: shell
+ - type: checkboxes
+ id: terms
+ attributes:
+ label: Code of Conduct
+ description: By submitting this issue, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md)
+ options:
+ - label: I agree to follow this project's Code of Conduct
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml
new file mode 100644
index 000000000..7f049a2ed
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/documentation.yml
@@ -0,0 +1,41 @@
+name: Documentation
+description: Update or add documentation
+title: "[DOCS]: "
+labels: ["Type: Documentation", "Status: Triage"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill this out!
+ - type: textarea
+ id: describe-need
+ attributes:
+ label: Describe the need
+ description: What do you wish was different about our docs?
+ placeholder: Describe the need for documentation updates here.
+ validations:
+ required: true
+ - type: input
+ id: sdk_version
+ attributes:
+ label: SDK Version
+ description: Do these docs apply to a specific SDK version?
+ placeholder: copilot-sdk-java v1.0.0
+ validations:
+ required: false
+ - type: textarea
+ id: logs
+ attributes:
+ label: Relevant log output
+ description: |
+ Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
+ Please check your logs before submission to ensure sensitive information is redacted.
+ render: shell
+ - type: checkboxes
+ id: terms
+ attributes:
+ label: Code of Conduct
+ description: By submitting this issue, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md)
+ options:
+ - label: I agree to follow this project's Code of Conduct
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml
new file mode 100644
index 000000000..377cd12ab
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature.yml
@@ -0,0 +1,41 @@
+name: Feature
+description: Suggest an idea for a new feature or enhancement
+title: "[FEAT]: "
+labels: ["Type: Feature", "Status: Triage"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill this out!
+ - type: textarea
+ id: describe-need
+ attributes:
+ label: Describe the need
+ description: What do you want to happen? What problem are you trying to solve?
+ placeholder: Describe the need for the feature.
+ validations:
+ required: true
+ - type: input
+ id: sdk_version
+ attributes:
+ label: SDK Version
+ description: Does this feature suggestion apply to a specific SDK version?
+ placeholder: copilot-sdk-java v1.0.0
+ validations:
+ required: false
+ - type: textarea
+ id: logs
+ attributes:
+ label: Relevant log output
+ description: |
+ Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
+ Please check your logs before submission to ensure sensitive information is redacted.
+ render: shell
+ - type: checkboxes
+ id: terms
+ attributes:
+ label: Code of Conduct
+ description: By submitting this issue, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md)
+ options:
+ - label: I agree to follow this project's Code of Conduct
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/maintenance.yml b/.github/ISSUE_TEMPLATE/maintenance.yml
new file mode 100644
index 000000000..de21996db
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/maintenance.yml
@@ -0,0 +1,41 @@
+name: Maintenance
+description: Dependencies, cleanup, refactoring, reworking of code
+title: "[MAINT]: "
+labels: ["Type: Maintenance", "Status: Triage"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill this out!
+ - type: textarea
+ id: describe-need
+ attributes:
+ label: Describe the need
+ description: What do you want to happen?
+ placeholder: Describe the maintenance need here.
+ validations:
+ required: true
+ - type: input
+ id: sdk_version
+ attributes:
+ label: SDK Version
+ description: Does this maintenance apply to a specific SDK version?
+ placeholder: copilot-sdk-java v1.0.0
+ validations:
+ required: false
+ - type: textarea
+ id: logs
+ attributes:
+ label: Relevant log output
+ description: |
+ Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
+ Please check your logs before submission to ensure sensitive information is redacted.
+ render: shell
+ - type: checkboxes
+ id: terms
+ attributes:
+ label: Code of Conduct
+ description: By submitting this issue, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md)
+ options:
+ - label: I agree to follow this project's Code of Conduct
+ required: true
diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg
index 738a6ad1e..8ef59f460 100644
--- a/.github/badges/jacoco.svg
+++ b/.github/badges/jacoco.svg
@@ -12,7 +12,7 @@
coveragecoverage
- 88.6%
- 88.6%
+ 87.5%
+ 87.5%
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 000000000..8993ca146
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,29 @@
+
+
+Resolves #ISSUE_NUMBER
+
+----
+
+### Before the change?
+
+
+*
+
+### After the change?
+
+
+*
+
+### Pull request checklist
+- [ ] Tests for the changes have been added (for bug fixes / features)
+- [ ] Docs have been reviewed and added / updated if needed (for bug fixes / features)
+- [ ] `mvn spotless:apply` has been run to format the code
+- [ ] `mvn clean verify` passes locally
+
+### Does this introduce a breaking change?
+
+
+- [ ] Yes
+- [ ] No
+
+----
diff --git a/.lastmerge b/.lastmerge
index 2a538cb8c..c5649a512 100644
--- a/.lastmerge
+++ b/.lastmerge
@@ -1 +1 @@
-dcd86c189501ce1b46b787ca60d90f3f315f3079
+062b61c8aa63b9b5d45fa1d7b01723e6660ffa83
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4845a214e..29e6b6281 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]
-> **Upstream sync:** [`github/copilot-sdk@dcd86c1`](https://github.com/github/copilot-sdk/commit/dcd86c189501ce1b46b787ca60d90f3f315f3079)
+> **Upstream sync:** [`github/copilot-sdk@062b61c`](https://github.com/github/copilot-sdk/commit/062b61c8aa63b9b5d45fa1d7b01723e6660ffa83)
+
+## [1.0.11] - 2026-03-12
+
+> **Upstream sync:** [`github/copilot-sdk@062b61c`](https://github.com/github/copilot-sdk/commit/062b61c8aa63b9b5d45fa1d7b01723e6660ffa83)
+### Added
+
+- `CopilotClientOptions.setOnListModels(Supplier>>)` — custom handler for `listModels()` used in BYOK mode to return models from a custom provider instead of querying the CLI (upstream: [`e478657`](https://github.com/github/copilot-sdk/commit/e478657))
+- `SessionConfig.setAgent(String)` — pre-selects a custom agent by name when creating a session (upstream: [`7766b1a`](https://github.com/github/copilot-sdk/commit/7766b1a))
+- `ResumeSessionConfig.setAgent(String)` — pre-selects a custom agent by name when resuming a session (upstream: [`7766b1a`](https://github.com/github/copilot-sdk/commit/7766b1a))
+- `SessionConfig.setOnEvent(Consumer)` — registers an event handler before the `session.create` RPC is issued, ensuring no early events are missed (upstream: [`4125fe7`](https://github.com/github/copilot-sdk/commit/4125fe7))
+- `ResumeSessionConfig.setOnEvent(Consumer)` — registers an event handler before the `session.resume` RPC is issued (upstream: [`4125fe7`](https://github.com/github/copilot-sdk/commit/4125fe7))
+- New broadcast session event types (protocol v3): `ExternalToolRequestedEvent` (`external_tool.requested`), `ExternalToolCompletedEvent` (`external_tool.completed`), `PermissionRequestedEvent` (`permission.requested`), `PermissionCompletedEvent` (`permission.completed`), `CommandQueuedEvent` (`command.queued`), `CommandCompletedEvent` (`command.completed`), `ExitPlanModeRequestedEvent` (`exit_plan_mode.requested`), `ExitPlanModeCompletedEvent` (`exit_plan_mode.completed`), `SystemNotificationEvent` (`system.notification`) (upstream: [`1653812`](https://github.com/github/copilot-sdk/commit/1653812), [`396e8b3`](https://github.com/github/copilot-sdk/commit/396e8b3))
+- `CopilotSession.log(String)` and `CopilotSession.log(String, String, Boolean)` — log a message to the session timeline (upstream: [`4125fe7`](https://github.com/github/copilot-sdk/commit/4125fe7))
+
+### Changed
+
+- **Protocol version bumped to v3.** The SDK now supports CLI servers running v2 or v3 (backward-compatible range). Sessions are now registered in the client's session map *before* the `session.create`/`session.resume` RPC is issued, ensuring broadcast events emitted immediately on session start are never dropped (upstream: [`4125fe7`](https://github.com/github/copilot-sdk/commit/4125fe7), [`1653812`](https://github.com/github/copilot-sdk/commit/1653812))
+- In protocol v3, tool calls and permission requests that have a registered handler are now handled automatically via `ExternalToolRequestedEvent` and `PermissionRequestedEvent` broadcast events; results are sent back via `session.tools.handlePendingToolCall` and `session.permissions.handlePendingPermissionRequest` RPC calls (upstream: [`1653812`](https://github.com/github/copilot-sdk/commit/1653812))
## [1.0.10] - 2026-03-03
@@ -409,9 +427,13 @@ New types: `GetForegroundSessionResponse`, `SetForegroundSessionResponse`
- Pre-commit hook for Spotless code formatting
- Comprehensive API documentation
-[Unreleased]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.10...HEAD
+[Unreleased]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.11...HEAD
+[1.0.11]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.10...v1.0.11
+[Unreleased]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.11...HEAD
+[1.0.11]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.10...v1.0.11
[1.0.10]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.9...v1.0.10
-[Unreleased]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.10...HEAD
+[Unreleased]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.11...HEAD
+[1.0.11]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.10...v1.0.11
[1.0.10]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.9...v1.0.10
[1.0.9]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.8...v1.0.9
[1.0.8]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.7...v1.0.8
diff --git a/README.md b/README.md
index 50d5cc4fd..4d31e5b33 100644
--- a/README.md
+++ b/README.md
@@ -33,14 +33,14 @@ Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build A
io.github.copilot-community-sdkcopilot-sdk
- 1.0.10
+ 1.0.11
```
### Gradle
```groovy
-implementation 'io.github.copilot-community-sdk:copilot-sdk:1.0.10'
+implementation 'io.github.copilot-community-sdk:copilot-sdk:1.0.11'
```
## Quick Start
@@ -102,9 +102,9 @@ jbang https://github.com/copilot-community-sdk/copilot-sdk-java/blob/latest/jban
### Quick Links
-- [Getting Started](https://copilot-community-sdk.github.io/copilot-sdk-java/documentation.html)
-- [Javadoc API Reference](https://copilot-community-sdk.github.io/copilot-sdk-java/apidocs/)
-- [MCP Servers Integration](https://copilot-community-sdk.github.io/copilot-sdk-java/mcp.html)
+- [Getting Started](https://copilot-community-sdk.github.io/copilot-sdk-java/latest/documentation.html)
+- [Javadoc API Reference](https://copilot-community-sdk.github.io/copilot-sdk-java/latest/apidocs/)
+- [MCP Servers Integration](https://copilot-community-sdk.github.io/copilot-sdk-java/latest/mcp.html)
- [Cookbook](src/site/markdown/cookbook/) — Practical recipes for common use cases
## Projects Using This SDK
diff --git a/jbang-example.java b/jbang-example.java
index d0b307e4e..c99178c6d 100644
--- a/jbang-example.java
+++ b/jbang-example.java
@@ -1,5 +1,5 @@
-//DEPS io.github.copilot-community-sdk:copilot-sdk:1.0.10
+//DEPS io.github.copilot-community-sdk:copilot-sdk:1.0.11
import com.github.copilot.sdk.*;
import com.github.copilot.sdk.events.*;
import com.github.copilot.sdk.json.*;
diff --git a/pom.xml b/pom.xml
index 19f69979c..51c08825f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
io.github.copilot-community-sdkcopilot-sdk
- 1.0.10
+ 1.0.11jarGitHub Copilot Community SDK :: Java
@@ -33,7 +33,7 @@
scm:git:https://github.com/copilot-community-sdk/copilot-sdk-java.gitscm:git:https://github.com/copilot-community-sdk/copilot-sdk-java.githttps://github.com/copilot-community-sdk/copilot-sdk-java
- v1.0.10
+ v1.0.11
diff --git a/src/main/java/com/github/copilot/sdk/CopilotClient.java b/src/main/java/com/github/copilot/sdk/CopilotClient.java
index 5c4fcfe21..39034c910 100644
--- a/src/main/java/com/github/copilot/sdk/CopilotClient.java
+++ b/src/main/java/com/github/copilot/sdk/CopilotClient.java
@@ -188,6 +188,8 @@ private CompletableFuture startCore() {
});
}
+ private static final int MIN_PROTOCOL_VERSION = 2;
+
private void verifyProtocolVersion(Connection connection) throws Exception {
int expectedVersion = SdkProtocolVersion.get();
var params = new HashMap();
@@ -200,15 +202,27 @@ private void verifyProtocolVersion(Connection connection) throws Exception {
+ "Please update your server to ensure compatibility.");
}
- if (pingResponse.protocolVersion() != expectedVersion) {
+ int serverVersion = pingResponse.protocolVersion();
+ if (serverVersion < MIN_PROTOCOL_VERSION || serverVersion > expectedVersion) {
throw new RuntimeException("SDK protocol version mismatch: SDK expects version " + expectedVersion
- + ", but server reports version " + pingResponse.protocolVersion() + ". "
+ + " (minimum " + MIN_PROTOCOL_VERSION + "), but server reports version " + serverVersion + ". "
+ "Please update your SDK or server to ensure compatibility.");
}
}
/**
- * Stops the client and closes all sessions.
+ * Disconnects from the Copilot server and closes all active sessions.
+ *
+ * This method performs graceful cleanup:
+ *
+ *
Closes all active sessions (releases in-memory resources)
+ *
Closes the JSON-RPC connection
+ *
Terminates the CLI server process (if spawned by this client)
+ *
+ *
+ * Note: session data on disk is preserved, so sessions can be resumed later. To
+ * permanently remove session data before stopping, call
+ * {@link #deleteSession(String)} for each session first.
*
* @return A future that completes when the client is stopped
*/
@@ -308,13 +322,32 @@ public CompletableFuture createSession(SessionConfig config) {
+ "new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)"));
}
return ensureConnected().thenCompose(connection -> {
- var request = SessionRequestBuilder.buildCreateRequest(config);
+ // Pre-generate session ID so the session can be registered before the RPC call,
+ // ensuring no events emitted by the CLI during creation are lost.
+ String sessionId = config.getSessionId() != null
+ ? config.getSessionId()
+ : java.util.UUID.randomUUID().toString();
+
+ var session = new CopilotSession(sessionId, connection.rpc);
+ SessionRequestBuilder.configureSession(session, config);
+ sessions.put(sessionId, session);
+
+ var request = SessionRequestBuilder.buildCreateRequest(config, sessionId);
return connection.rpc.invoke("session.create", request, CreateSessionResponse.class).thenApply(response -> {
- var session = new CopilotSession(response.sessionId(), connection.rpc, response.workspacePath());
- SessionRequestBuilder.configureSession(session, config);
- sessions.put(response.sessionId(), session);
+ session.setWorkspacePath(response.workspacePath());
+ // 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();
+ if (returnedId != null && !returnedId.equals(sessionId)) {
+ sessions.remove(sessionId);
+ session.setActiveSessionId(returnedId);
+ sessions.put(returnedId, session);
+ }
return session;
+ }).exceptionally(ex -> {
+ sessions.remove(sessionId);
+ throw ex instanceof RuntimeException re ? re : new RuntimeException(ex);
});
});
}
@@ -352,13 +385,26 @@ public CompletableFuture resumeSession(String sessionId, ResumeS
+ "new ResumeSessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)"));
}
return ensureConnected().thenCompose(connection -> {
+ // Register the session before the RPC call to avoid missing early events.
+ var session = new CopilotSession(sessionId, connection.rpc);
+ SessionRequestBuilder.configureSession(session, config);
+ sessions.put(sessionId, session);
+
var request = SessionRequestBuilder.buildResumeRequest(sessionId, config);
return connection.rpc.invoke("session.resume", request, ResumeSessionResponse.class).thenApply(response -> {
- var session = new CopilotSession(response.sessionId(), connection.rpc, response.workspacePath());
- SessionRequestBuilder.configureSession(session, config);
- sessions.put(response.sessionId(), session);
+ session.setWorkspacePath(response.workspacePath());
+ // If the server returned a different sessionId than what was requested, re-key.
+ String returnedId = response.sessionId();
+ if (returnedId != null && !returnedId.equals(sessionId)) {
+ sessions.remove(sessionId);
+ session.setActiveSessionId(returnedId);
+ sessions.put(returnedId, session);
+ }
return session;
+ }).exceptionally(ex -> {
+ sessions.remove(sessionId);
+ throw ex instanceof RuntimeException re ? re : new RuntimeException(ex);
});
});
}
@@ -423,6 +469,10 @@ public CompletableFuture getAuthStatus() {
*
* Results are cached after the first successful call to avoid rate limiting.
* The cache is cleared when the client disconnects.
+ *
+ * If an {@code onListModels} handler was provided in
+ * {@link com.github.copilot.sdk.json.CopilotClientOptions}, it is called
+ * instead of querying the CLI server. This is useful in BYOK mode.
*
* @return a future that resolves with a list of available models
* @see ModelInfo
@@ -434,6 +484,22 @@ public CompletableFuture> listModels() {
return CompletableFuture.completedFuture(new ArrayList<>(cached));
}
+ // If a custom handler is configured, use it instead of querying the CLI server
+ var onListModels = options.getOnListModels();
+ if (onListModels != null) {
+ synchronized (modelsCacheLock) {
+ if (modelsCache != null) {
+ return CompletableFuture.completedFuture(new ArrayList<>(modelsCache));
+ }
+ }
+ return onListModels.get().thenApply(models -> {
+ synchronized (modelsCacheLock) {
+ modelsCache = models;
+ }
+ return new ArrayList<>(models);
+ });
+ }
+
return ensureConnected().thenCompose(connection -> {
// Double-check cache inside lock
synchronized (modelsCacheLock) {
@@ -469,9 +535,12 @@ public CompletableFuture getLastSessionId() {
}
/**
- * Deletes a session by ID.
+ * Permanently deletes a session and all its data from disk, including
+ * conversation history, planning state, and artifacts.
*
- * This permanently removes the session and its conversation history.
+ * Unlike {@link CopilotSession#close()}, which only releases in-memory
+ * resources and preserves session data for later resumption, this method is
+ * irreversible. The session cannot be resumed after deletion.
*
* @param sessionId
* the ID of the session to delete
diff --git a/src/main/java/com/github/copilot/sdk/CopilotSession.java b/src/main/java/com/github/copilot/sdk/CopilotSession.java
index 1af22d803..452e82671 100644
--- a/src/main/java/com/github/copilot/sdk/CopilotSession.java
+++ b/src/main/java/com/github/copilot/sdk/CopilotSession.java
@@ -27,6 +27,8 @@
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.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;
@@ -38,6 +40,7 @@
import com.github.copilot.sdk.json.PermissionInvocation;
import com.github.copilot.sdk.json.PermissionRequest;
import com.github.copilot.sdk.json.PermissionRequestResult;
+import com.github.copilot.sdk.json.PermissionRequestResultKind;
import com.github.copilot.sdk.json.PostToolUseHookInput;
import com.github.copilot.sdk.json.PreToolUseHookInput;
import com.github.copilot.sdk.json.SendMessageRequest;
@@ -46,6 +49,7 @@
import com.github.copilot.sdk.json.SessionHooks;
import com.github.copilot.sdk.json.SessionStartHookInput;
import com.github.copilot.sdk.json.ToolDefinition;
+import com.github.copilot.sdk.json.ToolResultObject;
import com.github.copilot.sdk.json.UserInputHandler;
import com.github.copilot.sdk.json.UserInputInvocation;
import com.github.copilot.sdk.json.UserInputRequest;
@@ -58,6 +62,13 @@
* A session maintains conversation state, handles events, and manages tool
* execution. Sessions are created via {@link CopilotClient#createSession} or
* resumed via {@link CopilotClient#resumeSession}.
+ *
+ * {@code CopilotSession} implements {@link AutoCloseable}. Use the
+ * try-with-resources pattern for automatic cleanup, or call {@link #close()}
+ * explicitly. Closing a session releases in-memory resources but preserves
+ * session data on disk — the conversation can be resumed later via
+ * {@link CopilotClient#resumeSession}. To permanently delete session data, use
+ * {@link CopilotClient#deleteSession}.
*
*
Example Usage
*
@@ -93,8 +104,14 @@ public final class CopilotSession implements AutoCloseable {
private static final Logger LOG = Logger.getLogger(CopilotSession.class.getName());
private static final ObjectMapper MAPPER = JsonRpcClient.getObjectMapper();
- private final String sessionId;
- private final String workspacePath;
+ /**
+ * The current active session ID. Initialized to the pre-generated value and may
+ * be updated after session.create / session.resume if the server returns a
+ * different ID (e.g. when working against a v2 CLI that ignores the
+ * client-supplied sessionId).
+ */
+ private volatile String sessionId;
+ private volatile String workspacePath;
private final JsonRpcClient rpc;
private final Set> eventHandlers = ConcurrentHashMap.newKeySet();
private final Map toolHandlers = new ConcurrentHashMap<>();
@@ -150,6 +167,18 @@ public String getSessionId() {
return sessionId;
}
+ /**
+ * Updates the active session ID. Package-private; called by CopilotClient if
+ * the server returns a different session ID than the pre-generated one (e.g.
+ * when a v2 CLI ignores the client-supplied sessionId).
+ *
+ * @param sessionId
+ * the server-confirmed session ID
+ */
+ void setActiveSessionId(String sessionId) {
+ this.sessionId = sessionId;
+ }
+
/**
* Gets the path to the session workspace directory when infinite sessions are
* enabled.
@@ -163,6 +192,17 @@ public String getWorkspacePath() {
return workspacePath;
}
+ /**
+ * Sets the workspace path. Package-private; called by CopilotClient after
+ * session.create or session.resume RPC response.
+ *
+ * @param workspacePath
+ * the workspace path
+ */
+ void setWorkspacePath(String workspacePath) {
+ this.workspacePath = workspacePath;
+ }
+
/**
* Sets a custom error handler for exceptions thrown by event handlers.
*
@@ -544,6 +584,10 @@ public Closeable on(Class eventType, Consume
* @see #setEventErrorPolicy(EventErrorPolicy)
*/
void dispatchEvent(AbstractSessionEvent event) {
+ // Handle broadcast request events (protocol v3) before dispatching to user
+ // handlers. These are fire-and-forget: the response is sent asynchronously.
+ handleBroadcastEventAsync(event);
+
for (Consumer handler : eventHandlers) {
try {
handler.accept(event);
@@ -565,6 +609,136 @@ void dispatchEvent(AbstractSessionEvent event) {
}
}
+ /**
+ * Handles broadcast request events by executing local handlers and responding
+ * via RPC (protocol v3).
+ *
@@ -830,6 +1004,61 @@ public CompletableFuture setModel(String model) {
return rpc.invoke("session.model.switchTo", Map.of("sessionId", sessionId, "modelId", model), Void.class);
}
+ /**
+ * Logs a message to the session timeline.
+ *
+ * The message appears in the session event stream and is visible to SDK
+ * consumers. Non-ephemeral messages are also persisted to the session event log
+ * on disk.
+ *
+ *
+ *
+ * @param message
+ * the message to log
+ * @param level
+ * the log severity level ({@code "info"}, {@code "warning"},
+ * {@code "error"}), or {@code null} to use the default
+ * ({@code "info"})
+ * @param ephemeral
+ * when {@code true}, the message is transient and not persisted to
+ * disk; {@code null} uses default behavior
+ * @return a future that completes when the message is logged
+ * @throws IllegalStateException
+ * if this session has been terminated
+ */
+ public CompletableFuture log(String message, String level, Boolean ephemeral) {
+ ensureNotTerminated();
+ var params = new java.util.HashMap();
+ params.put("sessionId", sessionId);
+ params.put("message", message);
+ if (level != null) {
+ params.put("level", level);
+ }
+ if (ephemeral != null) {
+ params.put("ephemeral", ephemeral);
+ }
+ return rpc.invoke("session.log", params, Void.class);
+ }
+
+ /**
+ * Logs an informational message to the session timeline.
+ *
+ * @param message
+ * the message to log
+ * @return a future that completes when the message is logged
+ * @throws IllegalStateException
+ * if this session has been terminated
+ */
+ public CompletableFuture log(String message) {
+ return log(message, null, null);
+ }
+
/**
* Lists the custom agents available for selection in this session.
*
diff --git a/src/main/java/com/github/copilot/sdk/RpcHandlerDispatcher.java b/src/main/java/com/github/copilot/sdk/RpcHandlerDispatcher.java
index 671aea593..2f041c7e4 100644
--- a/src/main/java/com/github/copilot/sdk/RpcHandlerDispatcher.java
+++ b/src/main/java/com/github/copilot/sdk/RpcHandlerDispatcher.java
@@ -17,6 +17,7 @@
import com.github.copilot.sdk.events.AbstractSessionEvent;
import com.github.copilot.sdk.events.SessionEventParser;
import com.github.copilot.sdk.json.PermissionRequestResult;
+import com.github.copilot.sdk.json.PermissionRequestResultKind;
import com.github.copilot.sdk.json.SessionLifecycleEvent;
import com.github.copilot.sdk.json.SessionLifecycleEventMetadata;
import com.github.copilot.sdk.json.ToolDefinition;
@@ -183,7 +184,7 @@ private void handlePermissionRequest(JsonRpcClient rpc, String requestId, JsonNo
CopilotSession session = sessions.get(sessionId);
if (session == null) {
var result = new PermissionRequestResult()
- .setKind("denied-no-approval-rule-and-could-not-request-from-user");
+ .setKind(PermissionRequestResultKind.DENIED_COULD_NOT_REQUEST_FROM_USER);
rpc.sendResponse(Long.parseLong(requestId), Map.of("result", result));
return;
}
@@ -197,7 +198,7 @@ private void handlePermissionRequest(JsonRpcClient rpc, String requestId, JsonNo
}).exceptionally(ex -> {
try {
var result = new PermissionRequestResult()
- .setKind("denied-no-approval-rule-and-could-not-request-from-user");
+ .setKind(PermissionRequestResultKind.DENIED_COULD_NOT_REQUEST_FROM_USER);
rpc.sendResponse(Long.parseLong(requestId), Map.of("result", result));
} catch (IOException e) {
LOG.log(Level.SEVERE, "Error sending permission denied", e);
diff --git a/src/main/java/com/github/copilot/sdk/SdkProtocolVersion.java b/src/main/java/com/github/copilot/sdk/SdkProtocolVersion.java
index 40a7f9d56..3b00a88ae 100644
--- a/src/main/java/com/github/copilot/sdk/SdkProtocolVersion.java
+++ b/src/main/java/com/github/copilot/sdk/SdkProtocolVersion.java
@@ -14,7 +14,7 @@
*/
public enum SdkProtocolVersion {
- LATEST(2);
+ LATEST(3);
private int versionNumber;
diff --git a/src/main/java/com/github/copilot/sdk/SessionRequestBuilder.java b/src/main/java/com/github/copilot/sdk/SessionRequestBuilder.java
index 90f3c71d8..7ea0be880 100644
--- a/src/main/java/com/github/copilot/sdk/SessionRequestBuilder.java
+++ b/src/main/java/com/github/copilot/sdk/SessionRequestBuilder.java
@@ -27,20 +27,22 @@ private SessionRequestBuilder() {
*
* @param config
* the session configuration (may be null)
+ * @param sessionId
+ * the pre-generated session ID to use
* @return the built request object
*/
- static CreateSessionRequest buildCreateRequest(SessionConfig config) {
+ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sessionId) {
var request = new CreateSessionRequest();
// Always request permission callbacks to enable deny-by-default behavior
request.setRequestPermission(true);
// Always send envValueMode=direct for MCP servers
request.setEnvValueMode("direct");
+ request.setSessionId(sessionId);
if (config == null) {
return request;
}
request.setModel(config.getModel());
- request.setSessionId(config.getSessionId());
request.setClientName(config.getClientName());
request.setReasoningEffort(config.getReasoningEffort());
request.setTools(config.getTools());
@@ -54,6 +56,7 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config) {
request.setStreaming(config.isStreaming() ? true : null);
request.setMcpServers(config.getMcpServers());
request.setCustomAgents(config.getCustomAgents());
+ request.setAgent(config.getAgent());
request.setInfiniteSessions(config.getInfiniteSessions());
request.setSkillDirectories(config.getSkillDirectories());
request.setDisabledSkills(config.getDisabledSkills());
@@ -62,6 +65,22 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config) {
return request;
}
+ /**
+ * Builds a CreateSessionRequest from the given configuration.
+ *
+ * @param config
+ * the session configuration (may be null)
+ * @return the built request object
+ * @deprecated Use {@link #buildCreateRequest(SessionConfig, String)} instead.
+ */
+ @Deprecated
+ static CreateSessionRequest buildCreateRequest(SessionConfig config) {
+ String sessionId = (config != null && config.getSessionId() != null)
+ ? config.getSessionId()
+ : java.util.UUID.randomUUID().toString();
+ return buildCreateRequest(config, sessionId);
+ }
+
/**
* Builds a ResumeSessionRequest from the given session ID and configuration.
*
@@ -99,6 +118,7 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo
request.setStreaming(config.isStreaming() ? true : null);
request.setMcpServers(config.getMcpServers());
request.setCustomAgents(config.getCustomAgents());
+ request.setAgent(config.getAgent());
request.setSkillDirectories(config.getSkillDirectories());
request.setDisabledSkills(config.getDisabledSkills());
request.setInfiniteSessions(config.getInfiniteSessions());
@@ -131,6 +151,9 @@ static void configureSession(CopilotSession session, SessionConfig config) {
if (config.getHooks() != null) {
session.registerHooks(config.getHooks());
}
+ if (config.getOnEvent() != null) {
+ session.on(config.getOnEvent());
+ }
}
/**
@@ -158,5 +181,8 @@ static void configureSession(CopilotSession session, ResumeSessionConfig config)
if (config.getHooks() != null) {
session.registerHooks(config.getHooks());
}
+ if (config.getOnEvent() != null) {
+ session.on(config.getOnEvent());
+ }
}
}
diff --git a/src/main/java/com/github/copilot/sdk/events/AbstractSessionEvent.java b/src/main/java/com/github/copilot/sdk/events/AbstractSessionEvent.java
index 6c9d76e99..5127f6eee 100644
--- a/src/main/java/com/github/copilot/sdk/events/AbstractSessionEvent.java
+++ b/src/main/java/com/github/copilot/sdk/events/AbstractSessionEvent.java
@@ -63,6 +63,10 @@ public abstract sealed class AbstractSessionEvent permits
// Tool events
ToolUserRequestedEvent, ToolExecutionStartEvent, ToolExecutionPartialResultEvent, ToolExecutionProgressEvent,
ToolExecutionCompleteEvent,
+ // Broadcast request/completion events (protocol v3)
+ ExternalToolRequestedEvent, ExternalToolCompletedEvent, PermissionRequestedEvent, PermissionCompletedEvent,
+ CommandQueuedEvent, CommandCompletedEvent, ExitPlanModeRequestedEvent, ExitPlanModeCompletedEvent,
+ SystemNotificationEvent,
// User events
UserMessageEvent, PendingMessagesModifiedEvent,
// Skill events
diff --git a/src/main/java/com/github/copilot/sdk/events/CommandCompletedEvent.java b/src/main/java/com/github/copilot/sdk/events/CommandCompletedEvent.java
new file mode 100644
index 000000000..f9aeb0f3f
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/CommandCompletedEvent.java
@@ -0,0 +1,37 @@
+/*---------------------------------------------------------------------------------------------
+ * 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.completed
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class CommandCompletedEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private CommandCompletedData data;
+
+ @Override
+ public String getType() {
+ return "command.completed";
+ }
+
+ public CommandCompletedData getData() {
+ return data;
+ }
+
+ public void setData(CommandCompletedData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record CommandCompletedData(@JsonProperty("requestId") String requestId) {
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/CommandQueuedEvent.java b/src/main/java/com/github/copilot/sdk/events/CommandQueuedEvent.java
new file mode 100644
index 000000000..acd35a89c
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/CommandQueuedEvent.java
@@ -0,0 +1,38 @@
+/*---------------------------------------------------------------------------------------------
+ * 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.queued
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class CommandQueuedEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private CommandQueuedData data;
+
+ @Override
+ public String getType() {
+ return "command.queued";
+ }
+
+ public CommandQueuedData getData() {
+ return data;
+ }
+
+ public void setData(CommandQueuedData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record CommandQueuedData(@JsonProperty("requestId") String requestId,
+ @JsonProperty("command") String command) {
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/ExitPlanModeCompletedEvent.java b/src/main/java/com/github/copilot/sdk/events/ExitPlanModeCompletedEvent.java
new file mode 100644
index 000000000..217859e43
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/ExitPlanModeCompletedEvent.java
@@ -0,0 +1,37 @@
+/*---------------------------------------------------------------------------------------------
+ * 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: exit_plan_mode.completed
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class ExitPlanModeCompletedEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private ExitPlanModeCompletedData data;
+
+ @Override
+ public String getType() {
+ return "exit_plan_mode.completed";
+ }
+
+ public ExitPlanModeCompletedData getData() {
+ return data;
+ }
+
+ public void setData(ExitPlanModeCompletedData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record ExitPlanModeCompletedData(@JsonProperty("requestId") String requestId) {
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/ExitPlanModeRequestedEvent.java b/src/main/java/com/github/copilot/sdk/events/ExitPlanModeRequestedEvent.java
new file mode 100644
index 000000000..b0019c3ce
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/ExitPlanModeRequestedEvent.java
@@ -0,0 +1,39 @@
+/*---------------------------------------------------------------------------------------------
+ * 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: exit_plan_mode.requested
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class ExitPlanModeRequestedEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private ExitPlanModeRequestedData data;
+
+ @Override
+ public String getType() {
+ return "exit_plan_mode.requested";
+ }
+
+ public ExitPlanModeRequestedData getData() {
+ return data;
+ }
+
+ public void setData(ExitPlanModeRequestedData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record ExitPlanModeRequestedData(@JsonProperty("requestId") String requestId,
+ @JsonProperty("summary") String summary, @JsonProperty("planContent") String planContent,
+ @JsonProperty("actions") String[] actions, @JsonProperty("recommendedAction") String recommendedAction) {
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/ExternalToolCompletedEvent.java b/src/main/java/com/github/copilot/sdk/events/ExternalToolCompletedEvent.java
new file mode 100644
index 000000000..83a582720
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/ExternalToolCompletedEvent.java
@@ -0,0 +1,40 @@
+/*---------------------------------------------------------------------------------------------
+ * 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: external_tool.completed
+ *
+ * Broadcast when a pending tool call has been resolved by a client (protocol
+ * v3).
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class ExternalToolCompletedEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private ExternalToolCompletedData data;
+
+ @Override
+ public String getType() {
+ return "external_tool.completed";
+ }
+
+ public ExternalToolCompletedData getData() {
+ return data;
+ }
+
+ public void setData(ExternalToolCompletedData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record ExternalToolCompletedData(@JsonProperty("requestId") String requestId) {
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/ExternalToolRequestedEvent.java b/src/main/java/com/github/copilot/sdk/events/ExternalToolRequestedEvent.java
new file mode 100644
index 000000000..8eb11f5b8
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/ExternalToolRequestedEvent.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: external_tool.requested
+ *
+ * Broadcast when the CLI needs a client to handle a tool call (protocol v3).
+ * Clients that own the named tool should respond via
+ * {@code session.tools.handlePendingToolCall}.
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class ExternalToolRequestedEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private ExternalToolRequestedData data;
+
+ @Override
+ public String getType() {
+ return "external_tool.requested";
+ }
+
+ public ExternalToolRequestedData getData() {
+ return data;
+ }
+
+ public void setData(ExternalToolRequestedData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record ExternalToolRequestedData(@JsonProperty("requestId") String requestId,
+ @JsonProperty("sessionId") String sessionId, @JsonProperty("toolCallId") String toolCallId,
+ @JsonProperty("toolName") String toolName, @JsonProperty("arguments") Object arguments) {
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/PermissionCompletedEvent.java b/src/main/java/com/github/copilot/sdk/events/PermissionCompletedEvent.java
new file mode 100644
index 000000000..90daf3b49
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/PermissionCompletedEvent.java
@@ -0,0 +1,45 @@
+/*---------------------------------------------------------------------------------------------
+ * 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: permission.completed
+ *
+ * Broadcast when a pending permission request has been resolved by a client
+ * (protocol v3).
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class PermissionCompletedEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private PermissionCompletedData data;
+
+ @Override
+ public String getType() {
+ return "permission.completed";
+ }
+
+ public PermissionCompletedData getData() {
+ return data;
+ }
+
+ public void setData(PermissionCompletedData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record PermissionCompletedData(@JsonProperty("requestId") String requestId,
+ @JsonProperty("result") PermissionCompletedResult result) {
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record PermissionCompletedResult(@JsonProperty("kind") String kind) {
+ }
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/PermissionRequestedEvent.java b/src/main/java/com/github/copilot/sdk/events/PermissionRequestedEvent.java
new file mode 100644
index 000000000..d8f9ec147
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/PermissionRequestedEvent.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;
+import com.github.copilot.sdk.json.PermissionRequest;
+
+/**
+ * Event: permission.requested
+ *
+ * Broadcast when the CLI needs a client to handle a permission request
+ * (protocol v3). Clients that have a permission handler should respond via
+ * {@code session.permissions.handlePendingPermissionRequest}.
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class PermissionRequestedEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private PermissionRequestedData data;
+
+ @Override
+ public String getType() {
+ return "permission.requested";
+ }
+
+ public PermissionRequestedData getData() {
+ return data;
+ }
+
+ public void setData(PermissionRequestedData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record PermissionRequestedData(@JsonProperty("requestId") String requestId,
+ @JsonProperty("permissionRequest") PermissionRequest permissionRequest) {
+ }
+}
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 cfe8d5711..75971b29e 100644
--- a/src/main/java/com/github/copilot/sdk/events/SessionEventParser.java
+++ b/src/main/java/com/github/copilot/sdk/events/SessionEventParser.java
@@ -93,6 +93,15 @@ public class SessionEventParser {
TYPE_MAP.put("system.message", SystemMessageEvent.class);
TYPE_MAP.put("session.shutdown", SessionShutdownEvent.class);
TYPE_MAP.put("skill.invoked", SkillInvokedEvent.class);
+ TYPE_MAP.put("external_tool.requested", ExternalToolRequestedEvent.class);
+ TYPE_MAP.put("external_tool.completed", ExternalToolCompletedEvent.class);
+ TYPE_MAP.put("permission.requested", PermissionRequestedEvent.class);
+ TYPE_MAP.put("permission.completed", PermissionCompletedEvent.class);
+ TYPE_MAP.put("command.queued", CommandQueuedEvent.class);
+ TYPE_MAP.put("command.completed", CommandCompletedEvent.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/events/SystemNotificationEvent.java b/src/main/java/com/github/copilot/sdk/events/SystemNotificationEvent.java
new file mode 100644
index 000000000..38711f276
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/SystemNotificationEvent.java
@@ -0,0 +1,37 @@
+/*---------------------------------------------------------------------------------------------
+ * 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: system.notification
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class SystemNotificationEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private SystemNotificationData data;
+
+ @Override
+ public String getType() {
+ return "system.notification";
+ }
+
+ public SystemNotificationData getData() {
+ return data;
+ }
+
+ public void setData(SystemNotificationData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record SystemNotificationData(@JsonProperty("content") String content, @JsonProperty("kind") Object kind) {
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java b/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java
index 70ce99850..4fd55d3ba 100644
--- a/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java
+++ b/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java
@@ -4,7 +4,10 @@
package com.github.copilot.sdk.json;
+import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
import com.fasterxml.jackson.annotation.JsonInclude;
@@ -43,6 +46,7 @@ public class CopilotClientOptions {
private Map environment;
private String gitHubToken;
private Boolean useLoggedInUser;
+ private Supplier>> onListModels;
/**
* Gets the path to the Copilot CLI executable.
@@ -349,6 +353,31 @@ public CopilotClientOptions setUseLoggedInUser(Boolean useLoggedInUser) {
return this;
}
+ /**
+ * Gets the custom handler for listing available models.
+ *
+ * @return the handler, or {@code null} if not set
+ */
+ public Supplier>> getOnListModels() {
+ return onListModels;
+ }
+
+ /**
+ * Sets a custom handler for listing available models.
+ *
+ * When provided, {@code listModels()} calls this handler instead of querying
+ * the CLI server. Useful in BYOK (Bring Your Own Key) mode to return models
+ * available from your custom provider.
+ *
+ * @param onListModels
+ * the handler that returns the list of available models
+ * @return this options instance for method chaining
+ */
+ public CopilotClientOptions setOnListModels(Supplier>> onListModels) {
+ this.onListModels = onListModels;
+ return this;
+ }
+
/**
* Creates a shallow clone of this {@code CopilotClientOptions} instance.
*
@@ -374,6 +403,7 @@ public CopilotClientOptions clone() {
copy.environment = this.environment != null ? new java.util.HashMap<>(this.environment) : null;
copy.gitHubToken = this.gitHubToken;
copy.useLoggedInUser = this.useLoggedInUser;
+ copy.onListModels = this.onListModels;
return copy;
}
}
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 d73d82e6a..c0243f14b 100644
--- a/src/main/java/com/github/copilot/sdk/json/CreateSessionRequest.java
+++ b/src/main/java/com/github/copilot/sdk/json/CreateSessionRequest.java
@@ -76,6 +76,9 @@ public final class CreateSessionRequest {
@JsonProperty("customAgents")
private List customAgents;
+ @JsonProperty("agent")
+ private String agent;
+
@JsonProperty("infiniteSessions")
private InfiniteSessionConfig infiniteSessions;
@@ -260,6 +263,16 @@ public void setCustomAgents(List customAgents) {
this.customAgents = customAgents;
}
+ /** Gets the pre-selected agent name. @return the agent name */
+ public String getAgent() {
+ return agent;
+ }
+
+ /** Sets the pre-selected agent name. @param agent the agent name */
+ public void setAgent(String agent) {
+ this.agent = agent;
+ }
+
/** Gets infinite sessions config. @return the config */
public InfiniteSessionConfig getInfiniteSessions() {
return infiniteSessions;
diff --git a/src/main/java/com/github/copilot/sdk/json/PermissionHandler.java b/src/main/java/com/github/copilot/sdk/json/PermissionHandler.java
index e987e41ae..21b822e41 100644
--- a/src/main/java/com/github/copilot/sdk/json/PermissionHandler.java
+++ b/src/main/java/com/github/copilot/sdk/json/PermissionHandler.java
@@ -46,7 +46,7 @@ public interface PermissionHandler {
* @since 1.0.11
*/
PermissionHandler APPROVE_ALL = (request, invocation) -> CompletableFuture
- .completedFuture(new PermissionRequestResult().setKind("approved"));
+ .completedFuture(new PermissionRequestResult().setKind(PermissionRequestResultKind.APPROVED));
/**
* Handles a permission request from the assistant.
diff --git a/src/main/java/com/github/copilot/sdk/json/PermissionRequestResult.java b/src/main/java/com/github/copilot/sdk/json/PermissionRequestResult.java
index 12e2ec2c2..3d7390f03 100644
--- a/src/main/java/com/github/copilot/sdk/json/PermissionRequestResult.java
+++ b/src/main/java/com/github/copilot/sdk/json/PermissionRequestResult.java
@@ -17,13 +17,17 @@
*
*
Common Result Kinds
*
- *
"user-approved" - User approved the permission request
- *
"user-denied" - User denied the permission request
- *
"denied-no-approval-rule-and-could-not-request-from-user" - No handler
- * and couldn't ask user