en un clic
java-coding-skill
// Use this skill whenever editing `*.java` files in the `java/` SDK in order to write idiomatic, well-structured Java code for the Copilot SDK
// Use this skill whenever editing `*.java` files in the `java/` SDK in order to write idiomatic, well-structured Java code for the Copilot SDK
| name | java-coding-skill |
| description | Use this skill whenever editing `*.java` files in the `java/` SDK in order to write idiomatic, well-structured Java code for the Copilot SDK |
CompletableFuture for all async operationsAutoCloseable for resource cleanup (try-with-resources)<dependency>
<groupId>com.github</groupId>
<artifactId>copilot-sdk-java</artifactId>
<version>${copilot-sdk-java.version}</version>
</dependency>
implementation "com.github:copilot-sdk-java:${copilotSdkJavaVersion}"
try (var client = new CopilotClient()) {
client.start().get();
// Use client...
}
When creating a CopilotClient, use CopilotClientOptions:
cliPath - Path to CLI executable (default: "copilot" from PATH)cliArgs - Extra arguments prepended before SDK-managed flagscliUrl - URL of existing CLI server (e.g., "localhost:8080"). When provided, client won't spawn a processport - Server port (default: 0 for random, only when useStdio is false)useStdio - Use stdio transport instead of TCP (default: true)logLevel - Log level: "error", "warn", "info", "debug", "trace" (default: "info")autoStart - Auto-start server on first request (default: true)autoRestart - Auto-restart on crash (default: true)cwd - Working directory for the CLI processenvironment - Environment variables for the CLI processgitHubToken - GitHub token for authenticationuseLoggedInUser - Use logged-in gh CLI auth (default: true unless token provided)onListModels - Custom model list handler for BYOK scenariosvar options = new CopilotClientOptions()
.setCliPath("/path/to/copilot")
.setLogLevel("debug")
.setAutoStart(true)
.setAutoRestart(true)
.setGitHubToken(System.getenv("GITHUB_TOKEN"));
try (var client = new CopilotClient(options)) {
client.start().get();
// Use client...
}
For explicit control:
var client = new CopilotClient(new CopilotClientOptions().setAutoStart(false));
client.start().get();
// Use client...
client.stop().get();
Use forceStop() when stop() takes too long.
Use SessionConfig for configuration. The permission handler is required:
var session = client.createSession(new SessionConfig()
.setModel("gpt-5")
.setStreaming(true)
.setTools(List.of(...))
.setSystemMessage(new SystemMessageConfig()
.setMode(SystemMessageMode.APPEND)
.setContent("Custom instructions"))
.setAvailableTools(List.of("tool1", "tool2"))
.setExcludedTools(List.of("tool3"))
.setProvider(new ProviderConfig().setType("openai"))
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();
sessionId - Custom session IDclientName - Application namemodel - Model name ("gpt-5", "claude-sonnet-4.5", etc.)reasoningEffort - "low", "medium", "high", "xhigh"tools - Custom tools exposed to the CLIsystemMessage - System message customizationavailableTools - Allowlist of tool namesexcludedTools - Blocklist of tool namesprovider - Custom API provider configuration (BYOK)streaming - Enable streaming response chunks (default: false)workingDirectory - Session working directorymcpServers - MCP server configurationscustomAgents - Custom agent configurationsagent - Pre-select agent by nameinfiniteSessions - Infinite sessions configurationskillDirectories - Skill SKILL.md directoriesdisabledSkills - Skills to disableconfigDir - Config directory pathhooks - Session lifecycle hooksonPermissionRequest - REQUIRED permission handleronUserInputRequest - User input handleronEvent - Event handler registered before session creationAll setters return SessionConfig for method chaining.
var session = client.resumeSession(sessionId, new ResumeSessionConfig()
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();
session.getSessionId() - Get session identifiersession.send(prompt) / session.send(MessageOptions) - Send message, returns message IDsession.sendAndWait(prompt) / session.sendAndWait(MessageOptions) - Send and wait for response (60s timeout)session.sendAndWait(options, timeoutMs) - Send and wait with custom timeoutsession.abort() - Abort current processingsession.getMessages() - Get all events/messagessession.setModel(modelId) - Switch to a different modelsession.log(message) / session.log(message, "warning", false) / session.log(message, "error", false) - Log to session timeline with level "info", "warning", or "error"session.close() - Clean up resourcesUse CompletableFuture for waiting on session events:
var done = new CompletableFuture<Void>();
session.on(event -> {
if (event instanceof AssistantMessageEvent msg) {
System.out.println(msg.getData().content());
} else if (event instanceof SessionIdleEvent) {
done.complete(null);
}
});
session.send(new MessageOptions().setPrompt("Hello"));
done.get();
Use the typed on() overload for compile-time safety:
session.on(AssistantMessageEvent.class, msg -> {
System.out.println(msg.getData().content());
});
session.on(SessionIdleEvent.class, idle -> {
done.complete(null);
});
The on() method returns a Closeable:
var subscription = session.on(event -> { /* handler */ });
// Later...
subscription.close();
Use pattern matching (Java 17+) for event handling:
session.on(event -> {
if (event instanceof UserMessageEvent userMsg) {
// Handle user message
} else if (event instanceof AssistantMessageEvent assistantMsg) {
System.out.println(assistantMsg.getData().content());
} else if (event instanceof AssistantMessageDeltaEvent delta) {
System.out.print(delta.getData().deltaContent());
} else if (event instanceof ToolExecutionStartEvent toolStart) {
// Tool execution started
} else if (event instanceof ToolExecutionCompleteEvent toolComplete) {
// Tool execution completed
} else if (event instanceof SessionStartEvent start) {
// Session started
} else if (event instanceof SessionIdleEvent idle) {
// Session is idle (processing complete)
} else if (event instanceof SessionErrorEvent error) {
System.err.println("Error: " + error.getData().message());
}
});
Control how errors in event handlers are handled:
// Set a custom error handler
session.setEventErrorHandler(ex -> {
logger.error("Event handler error", ex);
});
// Or set the error propagation policy
session.setEventErrorPolicy(EventErrorPolicy.SUPPRESS_AND_LOG_ERRORS);
Set streaming(true) in SessionConfig:
var session = client.createSession(new SessionConfig()
.setModel("gpt-5")
.setStreaming(true)
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();
Handle both delta events (incremental) and final events:
var done = new CompletableFuture<Void>();
session.on(event -> {
switch (event) {
case AssistantMessageDeltaEvent delta ->
// Incremental text chunk
System.out.print(delta.getData().deltaContent());
case AssistantReasoningDeltaEvent reasoningDelta ->
// Incremental reasoning chunk (model-dependent)
System.out.print(reasoningDelta.getData().deltaContent());
case AssistantMessageEvent msg ->
// Final complete message
System.out.println("\n--- Final ---\n" + msg.getData().content());
case AssistantReasoningEvent reasoning ->
// Final reasoning content
System.out.println("--- Reasoning ---\n" + reasoning.getData().content());
case SessionIdleEvent idle ->
done.complete(null);
default -> { }
}
});
session.send(new MessageOptions().setPrompt("Tell me a story"));
done.get();
Note: Final events (AssistantMessageEvent, AssistantReasoningEvent) are ALWAYS sent regardless of streaming setting.
Use ToolDefinition.create() with JSON Schema parameters and a ToolHandler:
var tool = ToolDefinition.create(
"get_weather",
"Get weather for a location",
Map.of(
"type", "object",
"properties", Map.of(
"location", Map.of("type", "string", "description", "City name")
),
"required", List.of("location")
),
invocation -> {
String location = (String) invocation.getArguments().get("location");
return CompletableFuture.completedFuture("Sunny in " + location);
}
);
var session = client.createSession(new SessionConfig()
.setModel("gpt-5")
.setTools(List.of(tool))
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();
Use getArgumentsAs() for deserialization into a typed record or class:
record WeatherArgs(String location, String unit) {}
var tool = ToolDefinition.create(
"get_weather",
"Get weather for a location",
Map.of(
"type", "object",
"properties", Map.of(
"location", Map.of("type", "string"),
"unit", Map.of("type", "string", "enum", List.of("celsius", "fahrenheit"))
),
"required", List.of("location")
),
invocation -> {
var args = invocation.getArgumentsAs(WeatherArgs.class);
return CompletableFuture.completedFuture(
Map.of("temp", 72, "unit", args.unit(), "location", args.location())
);
}
);
var override = ToolDefinition.createOverride(
"built_in_tool_name",
"Custom description",
Map.of("type", "object", "properties", Map.of(...)),
invocation -> CompletableFuture.completedFuture("custom result")
);
When Copilot invokes a tool, the client automatically:
A permission handler is mandatory when creating or resuming sessions:
// Approve all requests (for development/testing)
new SessionConfig()
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
// Custom permission logic
new SessionConfig()
.setOnPermissionRequest((request, invocation) -> {
if ("dangerous-action".equals(request.getKind())) {
return CompletableFuture.completedFuture(
new PermissionRequestResult().setKind(PermissionRequestResultKind.DENIED)
);
}
return CompletableFuture.completedFuture(
new PermissionRequestResult().setKind(PermissionRequestResultKind.APPROVED)
);
})
Handle user input requests from the agent:
new SessionConfig()
.setOnUserInputRequest((request, invocation) -> {
System.out.println("Agent asks: " + request.getQuestion());
String answer = scanner.nextLine();
return CompletableFuture.completedFuture(
new UserInputResponse()
.setAnswer(answer)
.setWasFreeform(true)
);
})
var session = client.createSession(new SessionConfig()
.setModel("gpt-5")
.setSystemMessage(new SystemMessageConfig()
.setMode(SystemMessageMode.APPEND)
.setContent("""
<workflow_rules>
- Always check for security vulnerabilities
- Suggest performance improvements when applicable
</workflow_rules>
"""))
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();
var session = client.createSession(new SessionConfig()
.setModel("gpt-5")
.setSystemMessage(new SystemMessageConfig()
.setMode(SystemMessageMode.REPLACE)
.setContent("You are a helpful assistant."))
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();
Attach files to messages using Attachment:
session.send(new MessageOptions()
.setPrompt("Analyze this file")
.setAttachments(List.of(
new Attachment("file", "/path/to/file.java", "My File")
))
);
Use the mode property in MessageOptions:
"enqueue" - Queue message for processing (default)"immediate" - Process message immediatelysession.send(new MessageOptions()
.setPrompt("...")
.setMode("enqueue")
);
Use sendAndWait() to send a message and block until the assistant responds:
// With default 60-second timeout
AssistantMessageEvent response = session.sendAndWait("What is 2+2?").get();
System.out.println(response.getData().content());
// With custom timeout
AssistantMessageEvent response = session.sendAndWait(
new MessageOptions().setPrompt("Write a long story"),
120_000 // 120 seconds
).get();
Sessions are independent and can run concurrently:
var session1 = client.createSession(new SessionConfig()
.setModel("gpt-5")
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();
var session2 = client.createSession(new SessionConfig()
.setModel("claude-sonnet-4.5")
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();
session1.send(new MessageOptions().setPrompt("Hello from session 1"));
session2.send(new MessageOptions().setPrompt("Hello from session 2"));
Use custom API providers via ProviderConfig:
// OpenAI
var session = client.createSession(new SessionConfig()
.setProvider(new ProviderConfig()
.setType("openai")
.setBaseUrl("https://api.openai.com/v1")
.setApiKey("sk-..."))
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();
// Azure OpenAI
var session = client.createSession(new SessionConfig()
.setProvider(new ProviderConfig()
.setType("azure")
.setAzure(new AzureOptions()
.setEndpoint("https://my-resource.openai.azure.com")
.setDeployment("gpt-4"))
.setBearerToken("..."))
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();
var sessions = client.listSessions().get();
for (var metadata : sessions) {
System.out.println("Session: " + metadata.getSessionId());
}
client.deleteSession(sessionId).get();
var state = client.getState();
AutoCloseable subscription = client.onLifecycle(event -> {
System.out.println("Lifecycle event: " + event);
});
// Later...
subscription.close();
try {
var session = client.createSession(new SessionConfig()
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();
session.sendAndWait("Hello").get();
} catch (ExecutionException ex) {
Throwable cause = ex.getCause();
System.err.println("Error: " + cause.getMessage());
} catch (Exception ex) {
System.err.println("Error: " + ex.getMessage());
}
Monitor SessionErrorEvent for runtime errors:
session.on(SessionErrorEvent.class, error -> {
System.err.println("Session Error: " + error.getData().message());
});
Use ping() to verify server connectivity:
var response = client.ping("test message").get();
// Get CLI version and protocol info
var status = client.getStatus().get();
// Check authentication status
var authStatus = client.getAuthStatus().get();
// List available models
var models = client.listModels().get();
ALWAYS use try-with-resources for automatic disposal:
try (var client = new CopilotClient()) {
client.start().get();
try (var session = client.createSession(new SessionConfig()
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get()) {
// Use session...
}
}
// Resources automatically cleaned up
If not using try-with-resources:
var client = new CopilotClient();
try {
client.start().get();
// Use client...
} finally {
client.stop().get();
}
CopilotClient and CopilotSessioncreateSession and resumeSessionCompletableFuture properly - call .get() to block, or chain with .thenApply()/.thenCompose()sendAndWait() for simple request-response patterns instead of manual event handlingSessionErrorEvent for robust error handlingCloseable) when no longer neededSystemMessageMode.APPEND to preserve safety guardrailsgetArgumentsAs() for type-safe tool argument deserializationtry (var client = new CopilotClient()) {
client.start().get();
try (var session = client.createSession(new SessionConfig()
.setModel("gpt-5")
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get()) {
var response = session.sendAndWait("What is 2+2?").get();
System.out.println(response.getData().content());
}
}
try (var client = new CopilotClient()) {
client.start().get();
try (var session = client.createSession(new SessionConfig()
.setModel("gpt-5")
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get()) {
var done = new CompletableFuture<Void>();
session.on(AssistantMessageEvent.class, msg ->
System.out.println(msg.getData().content()));
session.on(SessionIdleEvent.class, idle ->
done.complete(null));
session.send(new MessageOptions().setPrompt("What is 2+2?"));
done.get();
}
}
try (var session = client.createSession(new SessionConfig()
.setModel("gpt-5")
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get()) {
var response1 = session.sendAndWait("What is the capital of France?").get();
System.out.println(response1.getData().content());
var response2 = session.sendAndWait("What is its population?").get();
System.out.println(response2.getData().content());
}
record UserInfo(String id, String name, String email, String role) {}
var tool = ToolDefinition.create(
"get_user",
"Retrieve user information",
Map.of(
"type", "object",
"properties", Map.of(
"userId", Map.of("type", "string", "description", "User ID")
),
"required", List.of("userId")
),
invocation -> {
String userId = (String) invocation.getArguments().get("userId");
return CompletableFuture.completedFuture(
new UserInfo(userId, "John Doe", "john@example.com", "Developer")
);
}
);
var session = client.createSession(new SessionConfig()
.setModel("gpt-5")
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
.setHooks(new SessionHooks()
.setOnPreToolUse((input, invocation) -> {
System.out.println("About to execute tool: " + input);
var decision = new PreToolUseHookOutput().setKind("allow");
return CompletableFuture.completedFuture(decision);
})
.setOnPostToolUse((output, invocation) -> {
System.out.println("Tool execution complete: " + output);
return CompletableFuture.completedFuture(null);
}))
).get();