Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .lastmerge
Original file line number Diff line number Diff line change
@@ -1 +1 @@
066a69c1e849adf1bd98564ab1b52316ec471182
30a76a535fcb094938e7920394865ba956c6172b
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
reference-impl-sync workflow and deal with the subsequent
PR.
-->
<readonly-copilot-sdk-ref-impl-version-from-lastmerge-file-updated-by-reference-impl-sync>^1.0.44-2</readonly-copilot-sdk-ref-impl-version-from-lastmerge-file-updated-by-reference-impl-sync>
<readonly-copilot-sdk-ref-impl-version-from-lastmerge-file-updated-by-reference-impl-sync>^1.0.44-3</readonly-copilot-sdk-ref-impl-version-from-lastmerge-file-updated-by-reference-impl-sync>

</properties>

Expand Down
56 changes: 28 additions & 28 deletions scripts/codegen/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion scripts/codegen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"generate:java": "tsx java.ts"
},
"dependencies": {
"@github/copilot": "^1.0.44-2",
"@github/copilot": "^1.0.44-3",
"json-schema": "^0.4.0",
"tsx": "^4.20.6"
}
Expand Down
92 changes: 92 additions & 0 deletions src/main/java/com/github/copilot/sdk/CopilotSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,20 @@
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.AutoModeSwitchHandler;
import com.github.copilot.sdk.json.AutoModeSwitchInvocation;
import com.github.copilot.sdk.json.AutoModeSwitchRequest;
import com.github.copilot.sdk.json.AutoModeSwitchResponse;
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.ExitPlanModeHandler;
import com.github.copilot.sdk.json.ExitPlanModeInvocation;
import com.github.copilot.sdk.json.ExitPlanModeRequest;
import com.github.copilot.sdk.json.ExitPlanModeResult;
import com.github.copilot.sdk.json.GetMessagesResponse;
import com.github.copilot.sdk.json.HookInvocation;
import com.github.copilot.sdk.json.InputOptions;
Expand Down Expand Up @@ -156,6 +164,8 @@ public final class CopilotSession implements AutoCloseable {
private final AtomicReference<PermissionHandler> permissionHandler = new AtomicReference<>();
private final AtomicReference<UserInputHandler> userInputHandler = new AtomicReference<>();
private final AtomicReference<ElicitationHandler> elicitationHandler = new AtomicReference<>();
private final AtomicReference<ExitPlanModeHandler> exitPlanModeHandler = new AtomicReference<>();
private final AtomicReference<AutoModeSwitchHandler> autoModeSwitchHandler = new AtomicReference<>();
private final AtomicReference<SessionHooks> hooksHandler = new AtomicReference<>();
private volatile EventErrorHandler eventErrorHandler;
private volatile EventErrorPolicy eventErrorPolicy = EventErrorPolicy.PROPAGATE_AND_LOG_ERRORS;
Expand Down Expand Up @@ -1317,6 +1327,32 @@ void registerElicitationHandler(ElicitationHandler handler) {
elicitationHandler.set(handler);
}

/**
* Registers an exit-plan-mode handler for this session.
* <p>
* Called internally when creating or resuming a session with an exit-plan-mode
* handler.
*
* @param handler
* the handler to invoke when an exit-plan-mode request is received
*/
void registerExitPlanModeHandler(ExitPlanModeHandler handler) {
exitPlanModeHandler.set(handler);
}

/**
* Registers an auto-mode-switch handler for this session.
* <p>
* Called internally when creating or resuming a session with an
* auto-mode-switch handler.
*
* @param handler
* the handler to invoke when an auto-mode-switch request is received
*/
void registerAutoModeSwitchHandler(AutoModeSwitchHandler handler) {
autoModeSwitchHandler.set(handler);
}

/**
* Sets the capabilities reported by the host for this session.
* <p>
Expand Down Expand Up @@ -1356,6 +1392,60 @@ CompletableFuture<UserInputResponse> handleUserInputRequest(UserInputRequest req
}
}

/**
* Handles an exit-plan-mode request from the Copilot CLI.
* <p>
* Called internally when the server requests to exit plan mode.
*
* @param request
* the exit-plan-mode request
* @return a future that resolves with the user's decision
*/
CompletableFuture<ExitPlanModeResult> handleExitPlanModeRequest(ExitPlanModeRequest request) {
ExitPlanModeHandler handler = exitPlanModeHandler.get();
if (handler == null) {
return CompletableFuture.completedFuture(new ExitPlanModeResult().setApproved(true));
}

try {
var invocation = new ExitPlanModeInvocation().setSessionId(sessionId);
return handler.handle(request, invocation).exceptionally(ex -> {
LOG.log(Level.SEVERE, "Exit plan mode handler threw an exception", ex);
throw new RuntimeException("Exit plan mode handler error", ex);
});
} catch (Exception e) {
LOG.log(Level.SEVERE, "Failed to process exit plan mode request", e);
return CompletableFuture.failedFuture(e);
}
}

/**
* Handles an auto-mode-switch request from the Copilot CLI.
* <p>
* Called internally when the server requests to switch to auto mode.
*
* @param request
* the auto-mode-switch request
* @return a future that resolves with the user's decision
*/
CompletableFuture<AutoModeSwitchResponse> handleAutoModeSwitchRequest(AutoModeSwitchRequest request) {
AutoModeSwitchHandler handler = autoModeSwitchHandler.get();
if (handler == null) {
return CompletableFuture.completedFuture(AutoModeSwitchResponse.NO);
}

try {
var invocation = new AutoModeSwitchInvocation().setSessionId(sessionId);
return handler.handle(request, invocation).exceptionally(ex -> {
LOG.log(Level.SEVERE, "Auto mode switch handler threw an exception", ex);
throw new RuntimeException("Auto mode switch handler error", ex);
});
} catch (Exception e) {
LOG.log(Level.SEVERE, "Failed to process auto mode switch request", e);
return CompletableFuture.failedFuture(e);
}
}

/**
* Registers hook handlers for this session.
* <p>
Expand Down Expand Up @@ -1850,6 +1940,8 @@ public void close() {
permissionHandler.set(null);
userInputHandler.set(null);
elicitationHandler.set(null);
exitPlanModeHandler.set(null);
autoModeSwitchHandler.set(null);
hooksHandler.set(null);
}

Expand Down
96 changes: 96 additions & 0 deletions src/main/java/com/github/copilot/sdk/RpcHandlerDispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.copilot.sdk.generated.SessionEvent;
import com.github.copilot.sdk.json.AutoModeSwitchRequest;
import com.github.copilot.sdk.json.ExitPlanModeRequest;
import com.github.copilot.sdk.json.PermissionRequestResult;
import com.github.copilot.sdk.json.PermissionRequestResultKind;
import com.github.copilot.sdk.json.SessionLifecycleEvent;
Expand Down Expand Up @@ -79,6 +81,10 @@ void registerHandlers(JsonRpcClient rpc) {
(requestId, params) -> handlePermissionRequest(rpc, requestId, params));
rpc.registerMethodHandler("userInput.request",
(requestId, params) -> handleUserInputRequest(rpc, requestId, params));
rpc.registerMethodHandler("exitPlanMode.request",
(requestId, params) -> handleExitPlanModeRequest(rpc, requestId, params));
rpc.registerMethodHandler("autoModeSwitch.request",
(requestId, params) -> handleAutoModeSwitchRequest(rpc, requestId, params));
rpc.registerMethodHandler("hooks.invoke", (requestId, params) -> handleHooksInvoke(rpc, requestId, params));
rpc.registerMethodHandler("systemMessage.transform",
(requestId, params) -> handleSystemMessageTransform(rpc, requestId, params));
Expand Down Expand Up @@ -283,6 +289,96 @@ private void handleUserInputRequest(JsonRpcClient rpc, String requestId, JsonNod
});
}

private void handleExitPlanModeRequest(JsonRpcClient rpc, String requestId, JsonNode params) {
runAsync(() -> {
try {
String sessionId = params.get("sessionId").asText();
String summary = params.has("summary") ? params.get("summary").asText() : "";
String planContent = params.has("planContent") && !params.get("planContent").isNull()
? params.get("planContent").asText()
: null;
String recommendedAction = params.has("recommendedAction")
? params.get("recommendedAction").asText()
: "autopilot";

var actions = new ArrayList<String>();
if (params.has("actions") && params.get("actions").isArray()) {
for (JsonNode action : params.get("actions")) {
actions.add(action.asText());
}
}

CopilotSession session = sessions.get(sessionId);
if (session == null) {
rpc.sendErrorResponse(Long.parseLong(requestId), -32602, "Unknown session " + sessionId);
return;
}

var request = new ExitPlanModeRequest().setSummary(summary).setPlanContent(planContent)
.setActions(actions).setRecommendedAction(recommendedAction);

session.handleExitPlanModeRequest(request).thenAccept(result -> {
try {
rpc.sendResponse(Long.parseLong(requestId), result);
} catch (IOException e) {
LOG.log(Level.SEVERE, "Error sending exit plan mode response", e);
}
}).exceptionally(ex -> {
try {
rpc.sendErrorResponse(Long.parseLong(requestId), -32603,
"Exit plan mode handler error: " + ex.getMessage());
} catch (IOException e) {
LOG.log(Level.SEVERE, "Error sending exit plan mode error", e);
}
return null;
});
} catch (Exception e) {
LOG.log(Level.SEVERE, "Error handling exit plan mode request", e);
}
});
}

private void handleAutoModeSwitchRequest(JsonRpcClient rpc, String requestId, JsonNode params) {
runAsync(() -> {
try {
String sessionId = params.get("sessionId").asText();
String errorCode = params.has("errorCode") && !params.get("errorCode").isNull()
? params.get("errorCode").asText()
: null;
Double retryAfterSeconds = params.has("retryAfterSeconds") && !params.get("retryAfterSeconds").isNull()
? params.get("retryAfterSeconds").asDouble()
: null;

CopilotSession session = sessions.get(sessionId);
if (session == null) {
rpc.sendErrorResponse(Long.parseLong(requestId), -32602, "Unknown session " + sessionId);
return;
}

var request = new AutoModeSwitchRequest().setErrorCode(errorCode)
.setRetryAfterSeconds(retryAfterSeconds);

session.handleAutoModeSwitchRequest(request).thenAccept(response -> {
try {
rpc.sendResponse(Long.parseLong(requestId), java.util.Map.of("response", response));
} catch (IOException e) {
LOG.log(Level.SEVERE, "Error sending auto mode switch response", e);
}
}).exceptionally(ex -> {
try {
rpc.sendErrorResponse(Long.parseLong(requestId), -32603,
"Auto mode switch handler error: " + ex.getMessage());
} catch (IOException e) {
LOG.log(Level.SEVERE, "Error sending auto mode switch error", e);
}
return null;
});
} catch (Exception e) {
LOG.log(Level.SEVERE, "Error handling auto mode switch request", e);
}
});
}

private void handleHooksInvoke(JsonRpcClient rpc, String requestId, JsonNode params) {
runAsync(() -> {
try {
Expand Down
Loading
Loading