Files
awesome-copilot/instructions/copilot-sdk-java.instructions.md
T
Ed Burns 59fdc7867a Prepare for archiving of copilot-sdk-java repo in favor of java directory in copilot-sdk (#1811)
* Prepare for archiving of copilot-sdk-java repo in favor of java directory in copilot-sdk

* Update README

* Update with latest `java` from `copilot-sdk`.

### 16:37 Prompt

I have added `copilot-sdk-00` to this VS Code workspace. The `copilot-sdk-00/java` directory **is** the Copilot SDK for which this `copilot-sdk-java.instructions.md` is the instructions.

Do a deep read of the source code in `copilot-sdk-00/java`.

Do a deep read of the existing `copilot-sdk-java.instructions.md`.

Suggest any updates based on what should be changed in `copilot-sdk-java.instructions.md`, based on your findings.

### 16:51 Response

Now I have a comprehensive picture. Let me summarize the findings and suggest updates:

---

Based on my deep read of the Java SDK source code and the current copilot-sdk-java.instructions.md, here are the updates I'd suggest:

## Issues Found

### 1. Incorrect: `session.setModel()` documentation is slightly wrong
The instructions show `session.setModel(modelId)` — this is actually correct (it exists on CopilotSession), but the full signatures should be documented:
- `setModel(String model)`
- `setModel(String model, String reasoningEffort)`
- `setModel(String model, String reasoningEffort, ModelCapabilitiesOverride modelCapabilities)` *(since 1.3.0)*

### 2. Incorrect: `PreToolUseHookOutput` API
The instructions show:
```java
var decision = new PreToolUseHookOutput().setKind("allow");
```
But actually `PreToolUseHookOutput` is a **record** with static factory methods:
- `PreToolUseHookOutput.allow()`
- `PreToolUseHookOutput.deny()`
- `PreToolUseHookOutput.deny(String reason)`
- `PreToolUseHookOutput.ask()`

### 3. Missing: `CopilotClientOptions` fields not documented
These are in the source but not in the instructions:
- `setRemote(boolean)` — Mission Control integration
- `setTelemetry(TelemetryConfig)` — OpenTelemetry export *(since 1.2.0)*
- `setSessionIdleTimeoutSeconds(int)` — Idle timeout *(since 1.3.0)*
- `setExecutor(Executor)` — Custom thread pool
- `setTcpConnectionToken(String)` — TCP security token

### 4. Missing: `ToolDefinition.createSkipPermission()`
A third factory method exists *(since 1.2.0)* that skips the CLI permission request entirely. The instructions only mention `create()` and `createOverride()`.

### 5. Missing: Session listing with filters
The instructions show `client.listSessions()` but don't mention the overload with `SessionListFilter` that can filter by `cwd`, `gitRoot`, `repository`, and `branch`.

### 6. Missing: `session.log()` with URL
The instructions show `log(message, level, ephemeral)` but there's also a 4-param overload:
```java
session.log(String message, String level, Boolean ephemeral, String url)
```

### 7. Missing: MCP Server Configuration
The instructions mention `setMcpServers()` in the SessionConfig options list but don't show how to configure them. There are two types:
- `McpStdioServerConfig` — Local/stdio-based servers
- `McpHttpServerConfig` — HTTP/SSE remote servers

### 8. Missing: Cloud Sessions / Remote features
The `setRemote(boolean)` option on client options and per-session GitHub token support for cloud/content-exclusion scenarios.

### 9. Missing: `ModelCapabilitiesOverride`
Vision/image support can be controlled via model capabilities override, but this isn't documented.

### 10. Missing: `EventErrorPolicy` enum values
The instructions mention `EventErrorPolicy.SUPPRESS_AND_LOG_ERRORS` but don't list the other value: `PROPAGATE_AND_LOG_ERRORS` (which is the default).

### 11. Missing: `onLifecycle()` typed overload
There's also `onLifecycle(String eventType, SessionLifecycleHandler handler)` for filtering specific lifecycle events.

### 12. Minor: `send()` return type
`session.send()` returns `CompletableFuture<String>` (the message ID), which is worth mentioning since it can be used for correlation.

---

## Recommended Priority

**High priority** (incorrect info):
- Fix #2 (`PreToolUseHookOutput` API)

**Medium priority** (missing important features):
- Add #4 (`createSkipPermission`)
- Add #7 (MCP server configuration examples)
- Add #3 (missing client options)
- Add #5 (session list filters)

**Low priority** (nice-to-have completeness):
- Expand #1 (setModel overloads)
- Add #6 (log with URL)
- Add #9-12 (minor additions)

Would you like me to apply any or all of these updates to the file?

### 16:52 Prompt

Yes. Apply all of these.

### 16:54 Response

Created 12 todos

Now I have the full file. Let me apply all the changes:

Starting: *Fix PreToolUseHookOutput API (incorrect)* (1/12)

Replacing 84 lines with 199 lines in [](file:///e%3A/workareas/awesome-copilot-00/instructions/copilot-sdk-java.instructions.md)

```

```

Completed: *Fix PreToolUseHookOutput API (incorrect)* (1/12)

All 12 updates have been applied to copilot-sdk-java.instructions.md:

1. **Fixed** `PreToolUseHookOutput` — now uses correct static factory methods (`allow()`, `deny()`, `ask()`) instead of the incorrect `new PreToolUseHookOutput().setKind("allow")`
2. **Added** `ToolDefinition.createSkipPermission()` section (since 1.2.0)
3. **Added** 5 missing `CopilotClientOptions` fields: `remote`, `telemetry`, `sessionIdleTimeoutSeconds`, `executor`, `tcpConnectionToken`
4. **Added** `SessionListFilter` with `setRepository()`, `setBranch()`, `setCwd()`, `setGitRoot()`
5. **Expanded** `setModel()` to show all 3 overloads (model, reasoning effort, capabilities)
6. **Added** `log(message, level, ephemeral, url)` overload
7. **Added** full MCP server configuration section with `McpStdioServerConfig` and `McpHttpServerConfig` examples
8. **Added** `EventErrorPolicy` enum values with explanations
9. **Added** `onLifecycle(String eventType, handler)` filtered overload
10. **Added** note that `send()` returns `CompletableFuture<String>` (message ID for correlation)
11. **Added** `ModelCapabilitiesOverride` section with vision/limits configuration (since 1.3.0)
12. **Added** `remote` option for Mission Control / cloud session integration
13. **Bonus:** Expanded `SessionHooks` example to include all 5 hook types (`onPreToolUse`, `onPostToolUse`, `onUserPromptSubmitted`, `onSessionStart`, `onSessionEnd`)

Made changes.

### 16:54 Prompt

In the "## Core Principles" section. Add a bullet point that describes how `Optional` is used in the API.

### 16:58 Response

Searched for text `Optional` (`copilot-sdk-00` · `java/src/main/java/com/github/copilot/sdk/json/*.java`), 10 results

Searched for text `Optional` (`copilot-sdk-00` · `java/src/main/java/com/github/copilot/sdk/json/InfiniteSessionConfig.java`), 13 results

Replacing 7 lines with 8 lines in [](file:///e%3A/workareas/awesome-copilot-00/instructions/copilot-sdk-java.instructions.md)

```

```
Added the bullet point describing how `Optional<T>`, `OptionalInt`, and `OptionalDouble` are used on config class getters to distinguish "not set" from explicit values, while setters accept raw types and return `this` for fluent chaining.

Made changes.

### 16:58 Prompt

Consider this code from the Java README.

```java
public class CopilotSDK {
    public static void main(String[] args) throws Exception {
        var lastMessage = new String[]{null};

        // Create and start client
        try (var client = new CopilotClient()) {  // JDK 25+: comment out this line
        // JDK 25+: uncomment the following 3 lines for virtual thread support
        // var options = new CopilotClientOptions()
        //     .setExecutor(Executors.newVirtualThreadPerTaskExecutor());
        // try (var client = new CopilotClient(options)) {
            client.start().get();

            // Create a session
            var session = client.createSession(
                new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("claude-sonnet-4.5")).get();

            // Handle assistant message events
            session.on(AssistantMessageEvent.class, msg -> {
                lastMessage[0] = msg.getData().content();
                System.out.println(lastMessage[0]);
            });

            // Handle session usage info events
            session.on(SessionUsageInfoEvent.class, usage -> {
                var data = usage.getData();
                System.out.println("\n--- Usage Metrics ---");
                System.out.println("Current tokens: " + data.currentTokens().intValue());
                System.out.println("Token limit: " + data.tokenLimit().intValue());
                System.out.println("Messages count: " + data.messagesLength().intValue());
            });

            // Send a message
            var completable = session.sendAndWait(new MessageOptions().setPrompt("What is 2+2?"));
            // and wait for completion
            completable.get();
        }

        boolean success = lastMessage[0] != null && lastMessage[0].contains("4");
        System.exit(success ? 0 : -1);
    }
}
```

I want to get across the very important JDK 25 comment. What's the best place to encode that knowledge in the instructions?

* Apply review suggestions
2026-05-25 11:29:34 +10:00

26 KiB

applyTo, description, name
applyTo description name
**/*.java, **/pom.xml This file provides guidance on building Java applications using GitHub Copilot SDK for Java. GitHub Copilot SDK Java Instructions

Core Principles

  • The SDK is in public preview and may have breaking changes
  • Requires Java 17 or later for baseline SDK usage. Some examples use newer JDK features and therefore require JDK 21 or later (for example, virtual threads via Executors.newVirtualThreadPerTaskExecutor() and switch pattern matching). Java 25 or later highly recommended.
  • Requires GitHub Copilot CLI installed and in PATH
  • Uses CompletableFuture for all async operations
  • Implements AutoCloseable for resource cleanup (try-with-resources)
  • Getters on configuration classes return Optional<T> (or OptionalInt/OptionalDouble) to distinguish "not set" from explicit values; setters accept raw types and return this for chaining. Use the clear methods to unset values if needed.

Installation

Maven

<dependency>
    <groupId>com.github</groupId>
    <artifactId>copilot-sdk-java</artifactId>
    <version>${copilot-sdk-java.version}</version>
</dependency>

Gradle

implementation "com.github:copilot-sdk-java:${copilotSdkJavaVersion}"

Client Initialization

Basic Client Setup

try (var client = new CopilotClient()) {
    client.start().get();
    // Use client...
}

Virtual Threads (JDK 25+)

Virtual threads were introduced in JDK 21, but significant performance bugs were not fixed until JDK 25, making JDK 25 the minimum recommended version for production use of virtual threads. On JDK 25+, use a virtual-thread executor for significantly better scalability. The SDK's async operations will run on virtual threads instead of the default ForkJoinPool:

var options = new CopilotClientOptions()
    .setExecutor(Executors.newVirtualThreadPerTaskExecutor());

try (var client = new CopilotClient(options)) {
    client.start().get();
    // Use client...
}

Client Configuration Options

When creating a CopilotClient, use CopilotClientOptions:

  • cliPath - Path to CLI executable (default: "copilot" from PATH)
  • cliArgs - Extra arguments prepended before SDK-managed flags
  • cliUrl - URL of existing CLI server (e.g., "localhost:8080"). When provided, client won't spawn a process
  • port - 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 process
  • environment - Environment variables for the CLI process
  • gitHubToken - GitHub token for authentication
  • useLoggedInUser - Use logged-in gh CLI auth (default: true unless token provided)
  • onListModels - Custom model list handler for BYOK scenarios
  • remote - Enable Mission Control / cloud session integration (default: false)
  • telemetry - TelemetryConfig for OpenTelemetry export (since 1.2.0)
  • sessionIdleTimeoutSeconds - Idle timeout before session auto-closes (since 1.3.0)
  • executor - Custom Executor for async operations (default: ForkJoinPool)
  • tcpConnectionToken - Security token for TCP transport authentication
var 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...
}

Manual Server Control

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.

Session Management

Creating Sessions

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();

Session Config Options

  • sessionId - Custom session ID
  • clientName - Application name
  • model - Model name ("gpt-5", "claude-sonnet-4.5", etc.)
  • reasoningEffort - "low", "medium", "high", "xhigh"
  • tools - Custom tools exposed to the CLI
  • systemMessage - System message customization
  • availableTools - Allowlist of tool names
  • excludedTools - Blocklist of tool names
  • provider - Custom API provider configuration (BYOK)
  • streaming - Enable streaming response chunks (default: false)
  • workingDirectory - Session working directory
  • mcpServers - MCP server configurations
  • customAgents - Custom agent configurations
  • agent - Pre-select agent by name
  • infiniteSessions - Infinite sessions configuration
  • skillDirectories - Skill SKILL.md directories
  • disabledSkills - Skills to disable
  • configDir - Config directory path
  • hooks - Session lifecycle hooks
  • onPermissionRequest - REQUIRED permission handler
  • onUserInputRequest - User input handler
  • onEvent - Event handler registered before session creation

All setters return SessionConfig for method chaining.

Resuming Sessions

var session = client.resumeSession(sessionId, new ResumeSessionConfig()
    .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();

Session Operations

  • session.getSessionId() - Get session identifier
  • session.send(prompt) / session.send(MessageOptions) - Send message, returns CompletableFuture<String> (the message ID, useful for correlation)
  • session.sendAndWait(prompt) / session.sendAndWait(MessageOptions) - Send and wait for response (60s timeout)
  • session.sendAndWait(options, timeoutMs) - Send and wait with custom timeout
  • session.abort() - Abort current processing
  • session.getMessages() - Get all events/messages
  • session.setModel(modelId) - Switch to a different model
  • session.setModel(modelId, reasoningEffort) - Switch model with reasoning effort ("low", "medium", "high", "xhigh")
  • session.setModel(modelId, reasoningEffort, modelCapabilities) - Switch model with ModelCapabilitiesOverride (since 1.3.0)
  • session.log(message) / session.log(message, "warning", false) / session.log(message, "error", false) - Log to session timeline with level "info", "warning", or "error"
  • session.log(message, level, ephemeral, url) - Log with a clickable URL link
  • session.close() - Clean up resources

Event Handling

Event Subscription Pattern

Use 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();

Type-Safe Event Handling

Use the typed on() overload for compile-time safety:

var done = new java.util.concurrent.CompletableFuture<Void>();

session.on(AssistantMessageEvent.class, msg -> {
    System.out.println(msg.getData().content());
});

session.on(SessionIdleEvent.class, idle -> {
    done.complete(null);
});

Unsubscribing from Events

The on() method returns a Closeable:

var subscription = session.on(event -> { /* handler */ });
// Later...
subscription.close();

Event Types

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());
    }
});

Event Error Handling

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);

EventErrorPolicy values:

  • PROPAGATE_AND_LOG_ERRORS - Stop event dispatch on error (default)
  • SUPPRESS_AND_LOG_ERRORS - Continue dispatch, log the error

Streaming Responses

Enabling Streaming

Set streaming(true) in SessionConfig:

var session = client.createSession(new SessionConfig()
    .setModel("gpt-5")
    .setStreaming(true)
    .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();

Handling Streaming Events

Handle both delta events (incremental) and final events:

Requires Java 21+, but 25 preferred.

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.

Custom Tools

Defining Tools

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();

Type-Safe Tool Arguments

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())
        );
    }
);

Overriding Built-In Tools

var override = ToolDefinition.createOverride(
    "built_in_tool_name",
    "Custom description",
    Map.of("type", "object", "properties", Map.of(...)),
    invocation -> CompletableFuture.completedFuture("custom result")
);

Skipping Permission Checks (since 1.2.0)

Use createSkipPermission() to define a tool that bypasses the CLI's permission request flow:

var tool = ToolDefinition.createSkipPermission(
    "safe_read_only_tool",
    "A tool that needs no permission confirmation",
    Map.of("type", "object", "properties", Map.of(...)),
    invocation -> CompletableFuture.completedFuture("result")
);

Tool Return Types

  • Return any JSON-serializable value (String, Map, List, record, POJO)
  • The SDK automatically serializes the return value and sends it back to the CLI

Tool Execution Flow

When Copilot invokes a tool, the client automatically:

  1. Deserializes the arguments
  2. Runs your handler function
  3. Serializes the return value
  4. Responds to the CLI

Permission Handling

Required Permission Handler

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)
        );
    })

User Input Handling

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)
        );
    })

System Message Customization

Append Mode (Default - Preserves Guardrails)

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();

Replace Mode (Full Control - Removes Guardrails)

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();

File Attachments

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")
    ))
);

Message Delivery Modes

Use the mode property in MessageOptions:

  • "enqueue" - Queue message for processing (default)
  • "immediate" - Process message immediately
session.send(new MessageOptions()
    .setPrompt("...")
    .setMode("enqueue")
);

Convenience: Send and Wait

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();

Multiple Sessions

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"));

Bring Your Own Key (BYOK)

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();

Session Lifecycle Management

Listing Sessions

var sessions = client.listSessions().get();
for (var metadata : sessions) {
    System.out.println("Session: " + metadata.getSessionId());
}

Filtering Sessions

Use SessionListFilter to narrow results by working directory, git root, repository, or branch:

var filter = new SessionListFilter()
    .setRepository("owner/repo")
    .setBranch("main");

var sessions = client.listSessions(filter).get();

Deleting Sessions

client.deleteSession(sessionId).get();

Checking Connection State

var state = client.getState();

Lifecycle Event Subscription

AutoCloseable subscription = client.onLifecycle(event -> {
    System.out.println("Lifecycle event: " + event);
});
// Later...
subscription.close();

Filtered Lifecycle Events

Subscribe to specific lifecycle event types:

AutoCloseable subscription = client.onLifecycle("session.created", event -> {
    System.out.println("New session created");
});

Error Handling

Standard Exception Handling

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());
}

Session Error Events

Monitor SessionErrorEvent for runtime errors:

session.on(SessionErrorEvent.class, error -> {
    System.err.println("Session Error: " + error.getData().message());
});

Connectivity Testing

Use ping() to verify server connectivity:

var response = client.ping("test message").get();

Status and Authentication

// 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();

Resource Cleanup

Automatic Cleanup with try-with-resources

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

Manual Cleanup

If not using try-with-resources:

var client = new CopilotClient();
try {
    client.start().get();
    // Use client...
} finally {
    client.stop().get();
}

Best Practices

  1. Always use try-with-resources for CopilotClient and CopilotSession
  2. Always provide a permission handler - it is required for createSession and resumeSession
  3. Use CompletableFuture properly - call .get() to block, or chain with .thenApply()/.thenCompose()
  4. Use sendAndWait() for simple request-response patterns instead of manual event handling
  5. Handle SessionErrorEvent for robust error handling
  6. Use pattern matching (switch with sealed types) for event handling
  7. Enable streaming for better UX in interactive scenarios
  8. Close event subscriptions (Closeable) when no longer needed
  9. Use SystemMessageMode.APPEND to preserve safety guardrails
  10. Provide descriptive tool names and descriptions for better model understanding
  11. Handle both delta and final events when streaming is enabled
  12. Use getArgumentsAs() for type-safe tool argument deserialization

Common Patterns

Simple Query-Response

try (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());
    }
}

Event-Driven Conversation

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();
    }
}

Multi-Turn Conversation

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());
}

Tool with Complex Return Type

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")
        );
    }
);

Session Hooks

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.getToolName());
            // Use static factory methods on PreToolUseHookOutput:
            // PreToolUseHookOutput.allow()
            // PreToolUseHookOutput.deny()
            // PreToolUseHookOutput.deny("reason")
            // PreToolUseHookOutput.ask()
            return CompletableFuture.completedFuture(PreToolUseHookOutput.allow());
        })
        .setOnPostToolUse((output, invocation) -> {
            System.out.println("Tool execution complete: " + output);
            return CompletableFuture.completedFuture(null);
        })
        .setOnUserPromptSubmitted((prompt, invocation) -> {
            // Intercept user prompts before processing
            return CompletableFuture.completedFuture(null);
        })
        .setOnSessionStart((event, invocation) -> {
            return CompletableFuture.completedFuture(null);
        })
        .setOnSessionEnd((event, invocation) -> {
            return CompletableFuture.completedFuture(null);
        }))
).get();

MCP Server Configuration

Configure Model Context Protocol servers via SessionConfig.setMcpServers():

Stdio-Based MCP Server

var mcpServers = Map.of(
    "my-server", new McpStdioServerConfig()
        .setCommand("node")
        .setArgs(List.of("path/to/server.js"))
);

var session = client.createSession(new SessionConfig()
    .setModel("gpt-5")
    .setMcpServers(mcpServers)
    .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();

HTTP/SSE MCP Server

var mcpServers = Map.of(
    "remote-server", new McpHttpServerConfig()
        .setUrl("https://my-mcp-server.example.com/sse")
);

var session = client.createSession(new SessionConfig()
    .setModel("gpt-5")
    .setMcpServers(mcpServers)
    .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();

Model Capabilities Override (since 1.3.0)

Override model capabilities for BYOK or custom providers:

var capabilities = new ModelCapabilitiesOverride()
    .setSupports(new ModelCapabilitiesOverride.Supports()
        .setVision(true)
        .setReasoningEffort(true))
    .setLimits(new ModelCapabilitiesOverride.Limits()
        .setMaxPromptTokens(128000));

var session = client.createSession(new SessionConfig()
    .setModel("custom-model")
    .setModelCapabilities(capabilities)
    .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();