Copilot SDK for Java - Documentation
⚠️ Disclaimer: This is an unofficial, community-driven SDK and is not supported or endorsed by GitHub. Use at your own risk.
This guide covers common use cases for the Copilot SDK for Java. For complete API details, see the Javadoc.
Table of Contents
- Basic Usage
- Handling Responses
- Troubleshooting Event Handling
- Event Types Reference
- Streaming Responses
- Session Operations
- Choosing a Model
- Reasoning Effort
- Tool Filtering
- Working Directory
- Connection State & Diagnostics
- Message Delivery Mode
- Session Management
- SessionConfig Reference
Basic Usage
Create a client, start a session, and send a message:
import com.github.copilot.sdk.*;
import com.github.copilot.sdk.json.*;
try (var client = new CopilotClient()) {
client.start().get();
var session = client.createSession(
new SessionConfig().setModel("gpt-4.1")
).get();
var response = session.sendAndWait("Explain Java records in one sentence").get();
System.out.println(response.getData().getContent());
session.close();
}
The client manages the connection to the Copilot CLI. Sessions are independent conversations that can run concurrently.
Handling Responses
Simple Request-Response
For straightforward interactions, use sendAndWait():
var response = session.sendAndWait("What is the capital of France?").get();
System.out.println(response.getData().getContent());
Event-Based Processing
For more control, subscribe to events and use send():
Exception isolation: If a handler throws an exception, the SDK logs the error. By default, dispatch stops after the first handler error (
PROPAGATE_AND_LOG_ERRORS). To continue dispatching to remaining handlers, setEventErrorPolicy.SUPPRESS_AND_LOG_ERRORS. You can customize error handling withsession.setEventErrorHandler()— see the Advanced Usage guide.
Troubleshooting Event Handling
Symptoms of policy misconfiguration
- You registered multiple
session.on(...)handlers, but only the first one runs - A handler throws once and later handlers stop receiving events
- You expected “best effort” fan-out, but dispatch halts on errors
Fix
Set the event error policy to suppress-and-continue:
session.setEventErrorPolicy(EventErrorPolicy.SUPPRESS_AND_LOG_ERRORS);
Optionally add a custom error handler for observability:
session.setEventErrorHandler((event, exception) -> {
System.err.println("Handler failed for event " + event.getType() + ": " + exception.getMessage());
});
Use PROPAGATE_AND_LOG_ERRORS when you want fail-fast behavior.
var done = new CompletableFuture<Void>();
// Type-safe event handlers (recommended)
session.on(AssistantMessageEvent.class, msg -> {
System.out.println("Response: " + msg.getData().getContent());
});
session.on(SessionErrorEvent.class, err -> {
System.err.println("Error: " + err.getData().message());
});
session.on(SessionIdleEvent.class, idle -> {
done.complete(null);
});
session.send("Tell me a joke").get();
done.get(); // Wait for completion
You can also use a single handler for all events:
session.on(event -> {
switch (event) {
case AssistantMessageEvent msg ->
System.out.println("Response: " + msg.getData().getContent());
case SessionErrorEvent err ->
System.err.println("Error: " + err.getData().message());
case SessionIdleEvent idle ->
done.complete(null);
default -> { }
}
});
Key Event Types
| Event | Description |
|---|---|
AssistantMessageEvent |
Complete assistant response |
AssistantMessageDeltaEvent |
Streaming chunk (when streaming enabled) |
SessionIdleEvent |
Session finished processing |
SessionErrorEvent |
An error occurred |
For the complete list of all event types, see Event Types Reference below.
Event Types Reference
The SDK supports event types organized by category. All events extend AbstractSessionEvent.
Session Events
| Event | Type String | Description |
|---|---|---|
SessionStartEvent |
session.start |
Session has started |
SessionResumeEvent |
session.resume |
Session was resumed |
SessionIdleEvent |
session.idle |
Session finished processing, ready for input |
SessionErrorEvent |
session.error |
An error occurred in the session |
SessionInfoEvent |
session.info |
Informational message from the session |
SessionShutdownEvent |
session.shutdown |
Session is shutting down (includes reason and exit code) |
SessionModelChangeEvent |
session.model_change |
The model was changed mid-session |
SessionHandoffEvent |
session.handoff |
Session handed off to another agent |
SessionTruncationEvent |
session.truncation |
Context was truncated due to limits |
SessionSnapshotRewindEvent |
session.snapshot_rewind |
Session rewound to a previous snapshot |
SessionUsageInfoEvent |
session.usage_info |
Token usage information |
SessionCompactionStartEvent |
session.compaction_start |
Context compaction started (infinite sessions) |
SessionCompactionCompleteEvent |
session.compaction_complete |
Context compaction completed |
Assistant Events
| Event | Type String | Description |
|---|---|---|
AssistantTurnStartEvent |
assistant.turn_start |
Assistant began processing |
AssistantIntentEvent |
assistant.intent |
Assistant's detected intent |
AssistantReasoningEvent |
assistant.reasoning |
Full reasoning content (reasoning models) |
AssistantReasoningDeltaEvent |
assistant.reasoning_delta |
Streaming reasoning chunk |
AssistantMessageEvent |
assistant.message |
Complete assistant message |
AssistantMessageDeltaEvent |
assistant.message_delta |
Streaming message chunk |
AssistantTurnEndEvent |
assistant.turn_end |
Assistant finished processing |
AssistantUsageEvent |
assistant.usage |
Token usage for this turn |
Tool Events
| Event | Type String | Description |
|---|---|---|
ToolUserRequestedEvent |
tool.user_requested |
User requested a tool invocation |
ToolExecutionStartEvent |
tool.execution_start |
Tool execution started |
ToolExecutionProgressEvent |
tool.execution_progress |
Tool execution progress update |
ToolExecutionPartialResultEvent |
tool.execution_partial_result |
Partial result from tool |
ToolExecutionCompleteEvent |
tool.execution_complete |
Tool execution completed |
User Events
| Event | Type String | Description |
|---|---|---|
UserMessageEvent |
user.message |
User sent a message |
PendingMessagesModifiedEvent |
pending_messages.modified |
Pending message queue changed |
Subagent Events
| Event | Type String | Description |
|---|---|---|
SubagentStartedEvent |
subagent.started |
Subagent was spawned |
SubagentSelectedEvent |
subagent.selected |
Subagent was selected for task |
SubagentCompletedEvent |
subagent.completed |
Subagent completed its task |
SubagentFailedEvent |
subagent.failed |
Subagent failed |
Other Events
| Event | Type String | Description |
|---|---|---|
AbortEvent |
abort |
Operation was aborted |
HookStartEvent |
hook.start |
Hook execution started |
HookEndEvent |
hook.end |
Hook execution completed |
SystemMessageEvent |
system.message |
System-level message |
SkillInvokedEvent |
skill.invoked |
A skill was invoked |
See the events package Javadoc for detailed event data structures.
Streaming Responses
Enable streaming to receive response chunks as they're generated:
var session = client.createSession(
new SessionConfig()
.setModel("gpt-4.1")
.setStreaming(true)
).get();
var done = new CompletableFuture<Void>();
session.on(AssistantMessageDeltaEvent.class, delta -> {
// Print each chunk as it arrives
System.out.print(delta.getData().deltaContent());
});
session.on(SessionIdleEvent.class, idle -> {
System.out.println(); // Newline at end
done.complete(null);
});
session.send("Write a haiku about Java").get();
done.get();
Session Operations
Get Conversation History
Retrieve all messages and events from a session using getMessages():
var history = session.getMessages().get();
for (var event : history) {
switch (event) {
case UserMessageEvent user ->
System.out.println("User: " + user.getData().getContent());
case AssistantMessageEvent assistant ->
System.out.println("Assistant: " + assistant.getData().getContent());
case ToolExecutionCompleteEvent tool ->
System.out.println("Tool: " + tool.getData().getToolName());
default -> { }
}
}
This is useful for:
- Displaying conversation history in a UI
- Persisting conversations for later review
- Debugging and logging session state
Abort Current Operation
Cancel a long-running operation using abort():
// Start a potentially long operation
var messageFuture = session.send("Analyze this large codebase...");
// User clicks cancel button
session.abort().get();
// The session will emit an AbortEvent
session.on(AbortEvent.class, evt -> {
System.out.println("Operation was cancelled");
});
Use cases:
- User-initiated cancellation in interactive applications
- Timeout handling in automated pipelines
- Graceful shutdown when application is closing
Custom Timeout
Use sendAndWait with a custom timeout for CI/CD pipelines:
try {
// 30-second timeout
var response = session.sendAndWait(
new MessageOptions().setPrompt("Quick question"),
30000 // timeout in milliseconds
).get();
} catch (ExecutionException e) {
if (e.getCause() instanceof TimeoutException) {
System.err.println("Request timed out");
session.abort().get();
}
}
Choosing a Model
List Available Models
Query available models before creating a session:
var models = client.listModels().get();
for (var model : models) {
System.out.printf("%s (%s)%n", model.getId(), model.getName());
}
Use a Specific Model
var session = client.createSession(
new SessionConfig().setModel("claude-sonnet-4")
).get();
Reasoning Effort
For models that support it, control how much effort the model spends reasoning before responding:
var session = client.createSession(
new SessionConfig()
.setModel("o3-mini")
.setReasoningEffort("high")
).get();
| Level | Description |
|---|---|
"low" |
Fastest responses, less detailed reasoning |
"medium" |
Balanced speed and reasoning depth |
"high" |
Thorough reasoning, slower responses |
"xhigh" |
Maximum reasoning effort |
Note: Only applies to reasoning models (e.g.,
o3-mini). Non-reasoning models ignore this setting.
Tool Filtering
Control which built-in tools the assistant can use with allowlists and blocklists.
Allowlist (Available Tools)
Restrict the session to only specific tools:
var session = client.createSession(
new SessionConfig()
.setAvailableTools(List.of("read_file", "search_code", "list_dir"))
).get();
When availableTools is set, the assistant can only use tools in this list.
Blocklist (Excluded Tools)
Allow all tools except specific ones:
var session = client.createSession(
new SessionConfig()
.setExcludedTools(List.of("execute_command", "write_file"))
).get();
Tools in the excludedTools list will not be available to the assistant.
Combining with Custom Tools
Tool filtering applies to built-in tools. Your custom tools (registered via setTools()) are always available:
var lookupTool = ToolDefinition.create("lookup_issue", "Fetch issue", schema, handler);
var session = client.createSession(
new SessionConfig()
.setTools(List.of(lookupTool)) // Custom tool always available
.setAvailableTools(List.of("read_file")) // Only allow read_file from built-ins
).get();
Working Directory
Set the working directory for file operations in the session:
var session = client.createSession(
new SessionConfig()
.setWorkingDirectory("/path/to/project")
).get();
This affects how the assistant resolves relative file paths when using tools like read_file, write_file, and search_code.
Connection State & Diagnostics
Checking Connection State
Query the client's connection state at any time without making a server call:
ConnectionState state = client.getState();
switch (state) {
case CONNECTED -> System.out.println("Ready");
case CONNECTING -> System.out.println("Starting up...");
case DISCONNECTED -> System.out.println("Not connected");
case ERROR -> System.out.println("Connection failed");
}
The four states are:
| State | Description |
|---|---|
DISCONNECTED |
Client has not been started yet |
CONNECTING |
start() was called but hasn't completed |
CONNECTED |
Client is connected and ready |
ERROR |
Connection failed (check logs for details) |
State Transitions
start()
DISCONNECTED ──────────► CONNECTING
│
┌──────────┼──────────┐
│ success │ │ failure
▼ │ ▼
CONNECTED │ ERROR
│ │
stop() / │ │
forceStop() │ │
▼ │
DISCONNECTED ◄───┘
│
│ start() (retry)
▼
CONNECTING
- DISCONNECTED → CONNECTING:
start()was called - CONNECTING → CONNECTED: Server connection and protocol handshake succeeded
- CONNECTING → ERROR: Connection or protocol verification failed
- CONNECTED → DISCONNECTED:
stop()orforceStop()was called, orclose()via try-with-resources - DISCONNECTED → CONNECTING:
start()can be called again to retry
Server Status
Get CLI version and protocol information:
var status = client.getStatus().get();
System.out.println("CLI version: " + status.getVersion());
System.out.println("Protocol version: " + status.getProtocolVersion());
Authentication Status
Check whether the current connection is authenticated and how:
var auth = client.getAuthStatus().get();
if (auth.isAuthenticated()) {
System.out.println("Logged in as: " + auth.getLogin());
System.out.println("Auth type: " + auth.getAuthType());
System.out.println("Host: " + auth.getHost());
} else {
System.out.println("Not authenticated: " + auth.getStatusMessage());
}
Ping
Verify server connectivity:
var pong = client.ping("hello").get();
System.out.println("Server responded, protocol version: " + pong.protocolVersion());
See ConnectionState, GetStatusResponse, and GetAuthStatusResponse Javadoc for details.
Message Delivery Mode
Control how messages are delivered to the session:
// Default: message is enqueued for processing
session.send(new MessageOptions()
.setPrompt("Analyze this codebase")
).get();
// Immediate: process the message right away
session.send(new MessageOptions()
.setPrompt("Quick question")
.setMode("immediate")
).get();
| Mode | Description |
|---|---|
"enqueue" |
Queue the message for processing (default) |
"immediate" |
Process the message immediately |
Session Management
Multiple Concurrent Sessions
var session1 = client.createSession(
new SessionConfig().setModel("gpt-4.1")
).get();
var session2 = client.createSession(
new SessionConfig().setModel("claude-sonnet-4")
).get();
// Send messages concurrently
var future1 = session1.sendAndWait("Summarize REST APIs");
var future2 = session2.sendAndWait("Summarize GraphQL");
System.out.println("GPT: " + future1.get().getData().getContent());
System.out.println("Claude: " + future2.get().getData().getContent());
Resume a Previous Session
// Get the last session ID
var lastSessionId = client.getLastSessionId().get();
// Or list all sessions
var sessions = client.listSessions().get();
// Resume a session
var session = client.resumeSession(lastSessionId).get();
Resume Options
When resuming a session, you can optionally reconfigure many settings. This is useful when you need to change the model, update tool configurations, or modify behavior.
| Option | Description |
|---|---|
model |
Change the model for the resumed session |
systemMessage |
Override or extend the system prompt |
availableTools |
Restrict which tools are available |
excludedTools |
Disable specific tools |
provider |
Re-provide BYOK credentials (required for BYOK sessions) |
reasoningEffort |
Adjust reasoning effort level |
streaming |
Enable/disable streaming responses |
workingDirectory |
Change the working directory |
configDir |
Override configuration directory |
mcpServers |
Configure MCP servers |
customAgents |
Configure custom agents |
skillDirectories |
Directories to load skills from |
disabledSkills |
Skills to disable |
infiniteSessions |
Configure infinite session behavior |
Example: Changing Model on Resume
// Resume with a different model
var config = new ResumeSessionConfig()
.setModel("claude-sonnet-4") // Switch to a different model
.setReasoningEffort("high"); // Increase reasoning effort
var session = client.resumeSession("user-123-task-456", config).get();
See ResumeSessionConfig Javadoc for complete options.
Clean Up Sessions
// Delete a specific session
client.deleteSession(sessionId).get();
SessionConfig Reference
Complete list of all SessionConfig options for createSession():
| Option | Type | Description | Guide |
|---|---|---|---|
sessionId |
String | Custom session ID (auto-generated if omitted) | — |
model |
String | AI model to use | Choosing a Model |
reasoningEffort |
String | Reasoning depth: "low", "medium", "high", "xhigh" |
Reasoning Effort |
tools |
List<ToolDefinition> | Custom tools the assistant can invoke | Custom Tools |
systemMessage |
SystemMessageConfig | Customize assistant behavior | System Messages |
availableTools |
List<String> | Allowlist of built-in tool names | Tool Filtering |
excludedTools |
List<String> | Blocklist of built-in tool names | Tool Filtering |
provider |
ProviderConfig | BYOK API provider configuration | BYOK |
onPermissionRequest |
PermissionHandler | Handler for permission requests | Permission Handling |
onUserInputRequest |
UserInputHandler | Handler for user input requests | User Input Handling |
hooks |
SessionHooks | Lifecycle and tool execution hooks | Session Hooks |
workingDirectory |
String | Working directory for file operations | Working Directory |
streaming |
boolean | Enable streaming response chunks | Streaming Responses |
mcpServers |
Map<String, Object> | MCP server configurations | MCP Servers |
customAgents |
List<CustomAgentConfig> | Custom agent definitions | Custom Agents |
infiniteSessions |
InfiniteSessionConfig | Auto-compaction for long conversations | Infinite Sessions |
skillDirectories |
List<String> | Directories to load skills from | Skills |
disabledSkills |
List<String> | Skills to disable by name | Skills |
configDir |
String | Custom configuration directory | Config Dir |
See SessionConfig Javadoc for full details.
Next Steps
- 📖 Advanced Usage - Tools, BYOK, MCP Servers, System Messages, Infinite Sessions, Skills
- 📖 Session Hooks - Intercept tool execution and session lifecycle events
- 📖 MCP Servers - Integrate external tools via Model Context Protocol
- 📖 API Javadoc - Complete API reference
