/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ package com.github.copilot.sdk; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import com.github.copilot.sdk.json.CopilotClientOptions; import com.github.copilot.sdk.json.CreateSessionResponse; import com.github.copilot.sdk.generated.rpc.ServerRpc; 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; import com.github.copilot.sdk.json.ModelInfo; import com.github.copilot.sdk.json.PingResponse; import com.github.copilot.sdk.json.ResumeSessionConfig; import com.github.copilot.sdk.json.ResumeSessionResponse; import com.github.copilot.sdk.json.SessionConfig; import com.github.copilot.sdk.json.SessionLifecycleHandler; import com.github.copilot.sdk.json.SessionListFilter; import com.github.copilot.sdk.json.SessionMetadata; /** * Provides a client for interacting with the Copilot CLI server. *
* The CopilotClient manages the connection to the Copilot CLI server and * provides methods to create and manage conversation sessions. It can either * spawn a CLI server process or connect to an existing server. *
* Example usage: * *
{@code
* try (var client = new CopilotClient()) {
* client.start().get();
*
* var session = client
* .createSession(
* new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("gpt-5"))
* .get();
*
* session.on(AssistantMessageEvent.class, msg -> {
* System.out.println(msg.getData().content());
* });
*
* session.send(new MessageOptions().setPrompt("Hello!")).get();
* }
* }
*
* @since 1.0.0
*/
public final class CopilotClient implements AutoCloseable {
private static final Logger LOG = Logger.getLogger(CopilotClient.class.getName());
/**
* Timeout, in seconds, used by {@link #close()} when waiting for graceful
* shutdown via {@link #stop()}.
*/
public static final int AUTOCLOSEABLE_TIMEOUT_SECONDS = 10;
private final CopilotClientOptions options;
private final CliServerManager serverManager;
private final LifecycleEventManager lifecycleManager = new LifecycleEventManager();
private final Map* This method performs graceful cleanup: *
* 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
*/
public CompletableFuture
* The session maintains conversation state and can be used to send messages and
* receive responses. Remember to close the session when done.
*
* A permission handler is required when creating a session. Use
* {@link com.github.copilot.sdk.json.PermissionHandler#APPROVE_ALL} to approve
* all permission requests, or provide a custom handler to control permissions
* selectively.
*
*
* Example:
*
*
* This restores a previously saved session, allowing you to continue a
* conversation. The session's history is preserved.
*
* A permission handler is required when resuming a session. Use
* {@link com.github.copilot.sdk.json.PermissionHandler#APPROVE_ALL} to approve
* all permission requests, or provide a custom handler to control permissions
* selectively.
*
* @param sessionId
* the ID of the session to resume
* @param config
* configuration for the resumed session, including the required
* {@link ResumeSessionConfig#setOnPermissionRequest(com.github.copilot.sdk.json.PermissionHandler)}
* handler
* @return a future that resolves with the resumed CopilotSession
* @throws IllegalArgumentException
* if {@code config} is {@code null} or does not have a permission
* handler set
* @see #listSessions()
* @see #getLastSessionId()
* @see com.github.copilot.sdk.json.PermissionHandler#APPROVE_ALL
*/
public CompletableFuture
* Provides strongly-typed access to all server-level API namespaces such as
* {@code models}, {@code tools}, {@code account}, and {@code mcp}.
*
* Example usage:
*
*
* This can be used to verify that the server is responsive and to check the
* protocol version.
*
* @param message
* an optional message to echo back
* @return a future that resolves with the ping response
* @see PingResponse
*/
public CompletableFuture
* 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
*/
public CompletableFuture
* This is useful for resuming the last conversation without needing to list all
* sessions.
*
* @return a future that resolves with the last session ID, or {@code null} if
* no sessions exist
* @see #resumeSession(String, com.github.copilot.sdk.json.ResumeSessionConfig)
*/
public CompletableFuture
* 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
* @return a future that completes when the session is deleted
* @throws RuntimeException
* if the deletion fails
*/
public CompletableFuture
* Returns metadata about all sessions that can be resumed, including their IDs,
* start times, and summaries.
*
* @return a future that resolves with a list of session metadata
* @see SessionMetadata
* @see #resumeSession(String, com.github.copilot.sdk.json.ResumeSessionConfig)
*/
public CompletableFuture
* Returns metadata about all sessions that can be resumed, including their IDs,
* start times, summaries, and context information. Use the filter parameter to
* narrow down sessions by working directory, git repository, or branch.
*
*
* This provides an efficient O(1) lookup of a single session's metadata instead
* of listing all sessions.
*
*
* This is only available when connecting to a server running in TUI+server mode
* (--ui-server).
*
* @return a future that resolves with the session ID, or null if no foreground
* session is set
*/
public CompletableFuture
* This is only available when connecting to a server running in TUI+server mode
* (--ui-server).
*
* @param sessionId
* the ID of the session to display in the TUI
* @return a future that completes when the operation is done
* @throws RuntimeException
* if the operation fails
*/
public CompletableFuture
* Lifecycle events are emitted when sessions are created, deleted, updated, or
* change foreground/background state (in TUI+server mode).
*
* @param handler
* a callback that receives lifecycle events
* @return an AutoCloseable that, when closed, unsubscribes the handler
*/
public AutoCloseable onLifecycle(SessionLifecycleHandler handler) {
return lifecycleManager.subscribe(handler);
}
/**
* Subscribes to a specific session lifecycle event type.
*
* @param eventType
* the event type to listen for (use
* {@link com.github.copilot.sdk.json.SessionLifecycleEventTypes}
* constants)
* @param handler
* a callback that receives events of the specified type
* @return an AutoCloseable that, when closed, unsubscribes the handler
*/
public AutoCloseable onLifecycle(String eventType, SessionLifecycleHandler handler) {
return lifecycleManager.subscribe(eventType, handler);
}
private CompletableFuture
* This method is intended for {@code try-with-resources} usage and blocks while
* waiting for {@link #stop()} to complete, up to
* {@link #AUTOCLOSEABLE_TIMEOUT_SECONDS} seconds. If shutdown fails or times
* out, the error is logged at {@link Level#FINE} and the method returns.
*
* This method is idempotent.
*
* @see #stop()
* @see #forceStop()
* @see #AUTOCLOSEABLE_TIMEOUT_SECONDS
*/
@Override
public void close() {
if (disposed)
return;
disposed = true;
try {
stop().get(AUTOCLOSEABLE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (Exception e) {
LOG.log(Level.FINE, "Error during close", e);
}
}
private static record Connection(JsonRpcClient rpc, Process process, ServerRpc serverRpc) {
};
}
{@code
* var session = client.createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get();
* }
*
* @param config
* configuration for the session, including the required
* {@link SessionConfig#setOnPermissionRequest(com.github.copilot.sdk.json.PermissionHandler)}
* handler
* @return a future that resolves with the created CopilotSession
* @throws IllegalArgumentException
* if {@code config} is {@code null} or does not have a permission
* handler set
* @see SessionConfig
* @see com.github.copilot.sdk.json.PermissionHandler#APPROVE_ALL
*/
public CompletableFuture{@code
* client.start().get();
* var models = client.getRpc().models.list().get();
* }
*
* @return the server-level typed RPC client
* @throws IllegalStateException
* if the client is not connected; call {@link #start()} first
* @since 1.0.0
*/
public ServerRpc getRpc() {
CompletableFuture> listModels() {
// Check cache first
List
> listSessions() {
return listSessions(null);
}
/**
* Lists all available sessions with optional filtering.
*
Example Usage
*
* {@code
* // List all sessions
* var allSessions = client.listSessions().get();
*
* // Filter by repository
* var filter = new SessionListFilter().setRepository("owner/repo");
* var repoSessions = client.listSessions(filter).get();
* }
*
* @param filter
* optional filter to narrow down sessions by context fields, or
* {@code null} to list all sessions
* @return a future that resolves with a list of session metadata
* @see SessionMetadata
* @see SessionListFilter
* @see #resumeSession(String, com.github.copilot.sdk.json.ResumeSessionConfig)
*/
public CompletableFuture> listSessions(SessionListFilter filter) {
return ensureConnected().thenCompose(connection -> {
Map
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