Skip to content

Commit cc84fab

Browse files
Copilotedburns
andauthored
Port Commands, Elicitation, and Capabilities features from upstream
Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/16d575c4-83f4-4d20-99d9-b48635b3791d Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
1 parent 844f3cf commit cc84fab

30 files changed

+1715
-5
lines changed

src/main/java/com/github/copilot/sdk/CopilotClient.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.github.copilot.sdk.json.DeleteSessionResponse;
2525
import com.github.copilot.sdk.json.GetAuthStatusResponse;
2626
import com.github.copilot.sdk.json.GetLastSessionIdResponse;
27+
import com.github.copilot.sdk.json.GetSessionMetadataResponse;
2728
import com.github.copilot.sdk.json.GetModelsResponse;
2829
import com.github.copilot.sdk.json.GetStatusResponse;
2930
import com.github.copilot.sdk.json.ListSessionsResponse;
@@ -374,6 +375,7 @@ public CompletableFuture<CopilotSession> createSession(SessionConfig config) {
374375

375376
return connection.rpc.invoke("session.create", request, CreateSessionResponse.class).thenApply(response -> {
376377
session.setWorkspacePath(response.workspacePath());
378+
session.setCapabilities(response.capabilities());
377379
// If the server returned a different sessionId (e.g. a v2 CLI that ignores
378380
// the client-supplied ID), re-key the sessions map.
379381
String returnedId = response.sessionId();
@@ -444,6 +446,7 @@ public CompletableFuture<CopilotSession> resumeSession(String sessionId, ResumeS
444446

445447
return connection.rpc.invoke("session.resume", request, ResumeSessionResponse.class).thenApply(response -> {
446448
session.setWorkspacePath(response.workspacePath());
449+
session.setCapabilities(response.capabilities());
447450
// If the server returned a different sessionId than what was requested, re-key.
448451
String returnedId = response.sessionId();
449452
if (returnedId != null && !returnedId.equals(sessionId)) {
@@ -657,6 +660,34 @@ public CompletableFuture<List<SessionMetadata>> listSessions(SessionListFilter f
657660
});
658661
}
659662

663+
/**
664+
* Gets metadata for a specific session by ID.
665+
* <p>
666+
* This provides an efficient O(1) lookup of a single session's metadata instead
667+
* of listing all sessions.
668+
*
669+
* <h2>Example Usage</h2>
670+
*
671+
* <pre>{@code
672+
* var metadata = client.getSessionMetadata("session-123").get();
673+
* if (metadata != null) {
674+
* System.out.println("Session started at: " + metadata.getStartTime());
675+
* }
676+
* }</pre>
677+
*
678+
* @param sessionId
679+
* the ID of the session to look up
680+
* @return a future that resolves with the {@link SessionMetadata}, or
681+
* {@code null} if the session was not found
682+
* @see SessionMetadata
683+
* @since 1.0.0
684+
*/
685+
public CompletableFuture<SessionMetadata> getSessionMetadata(String sessionId) {
686+
return ensureConnected().thenCompose(connection -> connection.rpc
687+
.invoke("session.getMetadata", Map.of("sessionId", sessionId), GetSessionMetadataResponse.class)
688+
.thenApply(GetSessionMetadataResponse::session));
689+
}
690+
660691
/**
661692
* Gets the ID of the session currently displayed in the TUI.
662693
* <p>

src/main/java/com/github/copilot/sdk/CopilotSession.java

Lines changed: 373 additions & 0 deletions
Large diffs are not rendered by default.

src/main/java/com/github/copilot/sdk/SessionRequestBuilder.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.util.function.Function;
1111

1212
import com.github.copilot.sdk.json.CreateSessionRequest;
13+
import com.github.copilot.sdk.json.CommandWireDefinition;
1314
import com.github.copilot.sdk.json.ResumeSessionConfig;
1415
import com.github.copilot.sdk.json.ResumeSessionRequest;
1516
import com.github.copilot.sdk.json.SectionOverride;
@@ -122,6 +123,16 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess
122123
request.setDisabledSkills(config.getDisabledSkills());
123124
request.setConfigDir(config.getConfigDir());
124125

126+
if (config.getCommands() != null && !config.getCommands().isEmpty()) {
127+
var wireCommands = config.getCommands().stream()
128+
.map(c -> new CommandWireDefinition(c.getName(), c.getDescription()))
129+
.collect(java.util.stream.Collectors.toList());
130+
request.setCommands(wireCommands);
131+
}
132+
if (config.getOnElicitationRequest() != null) {
133+
request.setRequestElicitation(true);
134+
}
135+
125136
return request;
126137
}
127138

@@ -183,6 +194,16 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo
183194
request.setDisabledSkills(config.getDisabledSkills());
184195
request.setInfiniteSessions(config.getInfiniteSessions());
185196

197+
if (config.getCommands() != null && !config.getCommands().isEmpty()) {
198+
var wireCommands = config.getCommands().stream()
199+
.map(c -> new CommandWireDefinition(c.getName(), c.getDescription()))
200+
.collect(java.util.stream.Collectors.toList());
201+
request.setCommands(wireCommands);
202+
}
203+
if (config.getOnElicitationRequest() != null) {
204+
request.setRequestElicitation(true);
205+
}
206+
186207
return request;
187208
}
188209

@@ -211,6 +232,12 @@ static void configureSession(CopilotSession session, SessionConfig config) {
211232
if (config.getHooks() != null) {
212233
session.registerHooks(config.getHooks());
213234
}
235+
if (config.getCommands() != null) {
236+
session.registerCommands(config.getCommands());
237+
}
238+
if (config.getOnElicitationRequest() != null) {
239+
session.registerElicitationHandler(config.getOnElicitationRequest());
240+
}
214241
if (config.getOnEvent() != null) {
215242
session.on(config.getOnEvent());
216243
}
@@ -241,6 +268,12 @@ static void configureSession(CopilotSession session, ResumeSessionConfig config)
241268
if (config.getHooks() != null) {
242269
session.registerHooks(config.getHooks());
243270
}
271+
if (config.getCommands() != null) {
272+
session.registerCommands(config.getCommands());
273+
}
274+
if (config.getOnElicitationRequest() != null) {
275+
session.registerElicitationHandler(config.getOnElicitationRequest());
276+
}
244277
if (config.getOnEvent() != null) {
245278
session.on(config.getOnEvent());
246279
}

src/main/java/com/github/copilot/sdk/events/AbstractSessionEvent.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ public abstract sealed class AbstractSessionEvent permits
6565
ToolExecutionCompleteEvent,
6666
// Broadcast request/completion events (protocol v3)
6767
ExternalToolRequestedEvent, ExternalToolCompletedEvent, PermissionRequestedEvent, PermissionCompletedEvent,
68-
CommandQueuedEvent, CommandCompletedEvent, ExitPlanModeRequestedEvent, ExitPlanModeCompletedEvent,
69-
SystemNotificationEvent,
68+
CommandQueuedEvent, CommandCompletedEvent, CommandExecuteEvent, ElicitationRequestedEvent,
69+
CapabilitiesChangedEvent, ExitPlanModeRequestedEvent, ExitPlanModeCompletedEvent, SystemNotificationEvent,
7070
// User events
7171
UserMessageEvent, PendingMessagesModifiedEvent,
7272
// Skill events
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
package com.github.copilot.sdk.events;
6+
7+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
8+
import com.fasterxml.jackson.annotation.JsonProperty;
9+
10+
/**
11+
* Event: capabilities.changed
12+
* <p>
13+
* Broadcast when the host's session capabilities change. The SDK updates
14+
* {@link com.github.copilot.sdk.CopilotSession#getCapabilities()} accordingly.
15+
*
16+
* @since 1.0.0
17+
*/
18+
@JsonIgnoreProperties(ignoreUnknown = true)
19+
public final class CapabilitiesChangedEvent extends AbstractSessionEvent {
20+
21+
@JsonProperty("data")
22+
private CapabilitiesChangedData data;
23+
24+
@Override
25+
public String getType() {
26+
return "capabilities.changed";
27+
}
28+
29+
public CapabilitiesChangedData getData() {
30+
return data;
31+
}
32+
33+
public void setData(CapabilitiesChangedData data) {
34+
this.data = data;
35+
}
36+
37+
@JsonIgnoreProperties(ignoreUnknown = true)
38+
public record CapabilitiesChangedData(@JsonProperty("ui") CapabilitiesChangedUi ui) {
39+
}
40+
41+
@JsonIgnoreProperties(ignoreUnknown = true)
42+
public record CapabilitiesChangedUi(@JsonProperty("elicitation") Boolean elicitation) {
43+
}
44+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
package com.github.copilot.sdk.events;
6+
7+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
8+
import com.fasterxml.jackson.annotation.JsonProperty;
9+
10+
/**
11+
* Event: command.execute
12+
* <p>
13+
* Broadcast when the user executes a slash command registered by this client.
14+
* Clients that have a matching command handler should respond via
15+
* {@code session.commands.handlePendingCommand}.
16+
*
17+
* @since 1.0.0
18+
*/
19+
@JsonIgnoreProperties(ignoreUnknown = true)
20+
public final class CommandExecuteEvent extends AbstractSessionEvent {
21+
22+
@JsonProperty("data")
23+
private CommandExecuteData data;
24+
25+
@Override
26+
public String getType() {
27+
return "command.execute";
28+
}
29+
30+
public CommandExecuteData getData() {
31+
return data;
32+
}
33+
34+
public void setData(CommandExecuteData data) {
35+
this.data = data;
36+
}
37+
38+
@JsonIgnoreProperties(ignoreUnknown = true)
39+
public record CommandExecuteData(@JsonProperty("requestId") String requestId,
40+
@JsonProperty("command") String command, @JsonProperty("commandName") String commandName,
41+
@JsonProperty("args") String args) {
42+
}
43+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
package com.github.copilot.sdk.events;
6+
7+
import java.util.List;
8+
import java.util.Map;
9+
10+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
11+
import com.fasterxml.jackson.annotation.JsonProperty;
12+
13+
/**
14+
* Event: elicitation.requested
15+
* <p>
16+
* Broadcast when the server or an MCP tool requests structured input from the
17+
* user. Clients that have an elicitation handler should respond via
18+
* {@code session.ui.handlePendingElicitation}.
19+
*
20+
* @since 1.0.0
21+
*/
22+
@JsonIgnoreProperties(ignoreUnknown = true)
23+
public final class ElicitationRequestedEvent extends AbstractSessionEvent {
24+
25+
@JsonProperty("data")
26+
private ElicitationRequestedData data;
27+
28+
@Override
29+
public String getType() {
30+
return "elicitation.requested";
31+
}
32+
33+
public ElicitationRequestedData getData() {
34+
return data;
35+
}
36+
37+
public void setData(ElicitationRequestedData data) {
38+
this.data = data;
39+
}
40+
41+
@JsonIgnoreProperties(ignoreUnknown = true)
42+
public record ElicitationRequestedData(@JsonProperty("requestId") String requestId,
43+
@JsonProperty("toolCallId") String toolCallId, @JsonProperty("elicitationSource") String elicitationSource,
44+
@JsonProperty("message") String message, @JsonProperty("mode") String mode,
45+
@JsonProperty("requestedSchema") ElicitationRequestedSchema requestedSchema,
46+
@JsonProperty("url") String url) {
47+
}
48+
49+
@JsonIgnoreProperties(ignoreUnknown = true)
50+
public record ElicitationRequestedSchema(@JsonProperty("type") String type,
51+
@JsonProperty("properties") Map<String, Object> properties,
52+
@JsonProperty("required") List<String> required) {
53+
}
54+
}

src/main/java/com/github/copilot/sdk/events/PermissionRequestedEvent.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public void setData(PermissionRequestedData data) {
3838

3939
@JsonIgnoreProperties(ignoreUnknown = true)
4040
public record PermissionRequestedData(@JsonProperty("requestId") String requestId,
41-
@JsonProperty("permissionRequest") PermissionRequest permissionRequest) {
41+
@JsonProperty("permissionRequest") PermissionRequest permissionRequest,
42+
@JsonProperty("resolvedByHook") Boolean resolvedByHook) {
4243
}
4344
}

src/main/java/com/github/copilot/sdk/events/SessionEventParser.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ public class SessionEventParser {
9999
TYPE_MAP.put("permission.completed", PermissionCompletedEvent.class);
100100
TYPE_MAP.put("command.queued", CommandQueuedEvent.class);
101101
TYPE_MAP.put("command.completed", CommandCompletedEvent.class);
102+
TYPE_MAP.put("command.execute", CommandExecuteEvent.class);
103+
TYPE_MAP.put("elicitation.requested", ElicitationRequestedEvent.class);
104+
TYPE_MAP.put("capabilities.changed", CapabilitiesChangedEvent.class);
102105
TYPE_MAP.put("exit_plan_mode.requested", ExitPlanModeRequestedEvent.class);
103106
TYPE_MAP.put("exit_plan_mode.completed", ExitPlanModeCompletedEvent.class);
104107
TYPE_MAP.put("system.notification", SystemNotificationEvent.class);
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
package com.github.copilot.sdk.json;
6+
7+
/**
8+
* Context passed to a {@link CommandHandler} when a slash command is executed.
9+
*
10+
* @since 1.0.0
11+
*/
12+
public class CommandContext {
13+
14+
private String sessionId;
15+
private String command;
16+
private String commandName;
17+
private String args;
18+
19+
/** Gets the session ID where the command was invoked. @return the session ID */
20+
public String getSessionId() {
21+
return sessionId;
22+
}
23+
24+
/** Sets the session ID. @param sessionId the session ID @return this */
25+
public CommandContext setSessionId(String sessionId) {
26+
this.sessionId = sessionId;
27+
return this;
28+
}
29+
30+
/**
31+
* Gets the full command text (e.g., {@code /deploy production}).
32+
*
33+
* @return the full command text
34+
*/
35+
public String getCommand() {
36+
return command;
37+
}
38+
39+
/** Sets the full command text. @param command the command text @return this */
40+
public CommandContext setCommand(String command) {
41+
this.command = command;
42+
return this;
43+
}
44+
45+
/**
46+
* Gets the command name without the leading {@code /}.
47+
*
48+
* @return the command name
49+
*/
50+
public String getCommandName() {
51+
return commandName;
52+
}
53+
54+
/** Sets the command name. @param commandName the command name @return this */
55+
public CommandContext setCommandName(String commandName) {
56+
this.commandName = commandName;
57+
return this;
58+
}
59+
60+
/**
61+
* Gets the raw argument string after the command name.
62+
*
63+
* @return the argument string
64+
*/
65+
public String getArgs() {
66+
return args;
67+
}
68+
69+
/** Sets the argument string. @param args the argument string @return this */
70+
public CommandContext setArgs(String args) {
71+
this.args = args;
72+
return this;
73+
}
74+
}

0 commit comments

Comments
 (0)