Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .lastmerge
Original file line number Diff line number Diff line change
@@ -1 +1 @@
062b61c8aa63b9b5d45fa1d7b01723e6660ffa83
ea90f076091371810c66d05590f65e2863f79bdf
23 changes: 23 additions & 0 deletions src/main/java/com/github/copilot/sdk/CliServerManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.regex.Pattern;

import com.github.copilot.sdk.json.CopilotClientOptions;
import com.github.copilot.sdk.json.TelemetryConfig;

/**
* Manages the lifecycle of the Copilot CLI server process.
Expand Down Expand Up @@ -110,6 +111,28 @@ ProcessInfo startCliServer() throws IOException, InterruptedException {
pb.environment().put("COPILOT_SDK_AUTH_TOKEN", options.getGitHubToken());
}

// Set telemetry environment variables if configured
TelemetryConfig telemetry = options.getTelemetry();
if (telemetry != null) {
pb.environment().put("COPILOT_OTEL_ENABLED", "true");
if (telemetry.getOtlpEndpoint() != null) {
pb.environment().put("OTEL_EXPORTER_OTLP_ENDPOINT", telemetry.getOtlpEndpoint());
}
if (telemetry.getFilePath() != null) {
pb.environment().put("COPILOT_OTEL_FILE_EXPORTER_PATH", telemetry.getFilePath());
}
if (telemetry.getExporterType() != null) {
pb.environment().put("COPILOT_OTEL_EXPORTER_TYPE", telemetry.getExporterType());
}
if (telemetry.getSourceName() != null) {
pb.environment().put("COPILOT_OTEL_SOURCE_NAME", telemetry.getSourceName());
}
if (telemetry.getCaptureContent() != null) {
pb.environment().put("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT",
telemetry.getCaptureContent() ? "true" : "false");
}
}

Process process = pb.start();

// Forward stderr to logger in background
Expand Down
34 changes: 33 additions & 1 deletion src/main/java/com/github/copilot/sdk/CopilotSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public final class CopilotSession implements AutoCloseable {
private final AtomicReference<UserInputHandler> userInputHandler = new AtomicReference<>();
private final AtomicReference<SessionHooks> hooksHandler = new AtomicReference<>();
private volatile EventErrorHandler eventErrorHandler;
private volatile EventErrorPolicy eventErrorPolicy = EventErrorPolicy.PROPAGATE_AND_LOG_ERRORS;
private volatile EventErrorPolicy eventErrorPolicy = EventErrorPolicy.SUPPRESS_AND_LOG_ERRORS;

/** Tracks whether this session instance has been terminated via close(). */
private volatile boolean isTerminated = false;
Expand Down Expand Up @@ -709,6 +709,9 @@ private void executePermissionAndRespondAsync(String requestId, PermissionReques
invocation.setSessionId(sessionId);
handler.handle(permissionRequest, invocation).thenAccept(result -> {
try {
if (PermissionRequestResultKind.NO_RESULT.equals(result.getKind())) {
return; // Leave the permission request unanswered (for extensions)
}
rpc.invoke("session.permissions.handlePendingPermissionRequest",
Map.of("sessionId", sessionId, "requestId", requestId, "result", result), Object.class);
} catch (Exception e) {
Expand Down Expand Up @@ -1000,7 +1003,36 @@ public CompletableFuture<Void> abort() {
* @since 1.0.11
*/
public CompletableFuture<Void> setModel(String model) {
return setModel(model, null);
}

/**
* Changes the model and reasoning effort for this session.
* <p>
* The new model and reasoning effort take effect for the next message.
* Conversation history is preserved.
*
* <pre>{@code
* session.setModel("gpt-4.1", "high").get();
* }</pre>
*
* @param model
* the model ID to switch to (e.g., {@code "gpt-4.1"})
* @param reasoningEffort
* the reasoning effort level (e.g., {@code "low"}, {@code "medium"},
* {@code "high"}, {@code "xhigh"}), or {@code null} to use the
* default
* @return a future that completes when the model switch is acknowledged
* @throws IllegalStateException
* if this session has been terminated
* @since 1.1.0
*/
public CompletableFuture<Void> setModel(String model, String reasoningEffort) {
ensureNotTerminated();
if (reasoningEffort != null) {
return rpc.invoke("session.model.switchTo",
Map.of("sessionId", sessionId, "modelId", model, "reasoningEffort", reasoningEffort), Void.class);
}
return rpc.invoke("session.model.switchTo", Map.of("sessionId", sessionId, "modelId", model), Void.class);
}

Expand Down
14 changes: 7 additions & 7 deletions src/main/java/com/github/copilot/sdk/EventErrorPolicy.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@
* <b>Example:</b>
*
* <pre>{@code
* // Default: propagate errors (stop dispatch on first error, log the error)
* session.setEventErrorPolicy(EventErrorPolicy.PROPAGATE_AND_LOG_ERRORS);
*
* // Opt-in to suppress errors (continue dispatching, log each error)
* // Default: suppress errors (continue dispatching after errors, log each
* // error)
* session.setEventErrorPolicy(EventErrorPolicy.SUPPRESS_AND_LOG_ERRORS);
*
* // Opt-in to propagate errors (stop dispatch on first error, log the error)
* session.setEventErrorPolicy(EventErrorPolicy.PROPAGATE_AND_LOG_ERRORS);
* }</pre>
*
* @see CopilotSession#setEventErrorPolicy(EventErrorPolicy)
Expand All @@ -44,7 +45,7 @@ public enum EventErrorPolicy {

/**
* Suppress errors: log the error and continue dispatching to remaining
* listeners.
* listeners (default).
* <p>
* When a handler throws an exception, the error is logged at
* {@link java.util.logging.Level#WARNING} and remaining handlers still execute.
Expand All @@ -54,8 +55,7 @@ public enum EventErrorPolicy {
SUPPRESS_AND_LOG_ERRORS,

/**
* Propagate errors: log the error and stop dispatch on first listener error
* (default).
* Propagate errors: log the error and stop dispatch on first listener error.
* <p>
* When a handler throws an exception, the error is logged at
* {@link java.util.logging.Level#WARNING} and no further handlers are invoked.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public abstract sealed class AbstractSessionEvent permits
SessionModelChangeEvent, SessionModeChangedEvent, SessionPlanChangedEvent, SessionWorkspaceFileChangedEvent,
SessionHandoffEvent, SessionTruncationEvent, SessionSnapshotRewindEvent, SessionUsageInfoEvent,
SessionCompactionStartEvent, SessionCompactionCompleteEvent, SessionShutdownEvent, SessionContextChangedEvent,
SessionTaskCompleteEvent,
SessionTaskCompleteEvent, SessionToolsUpdatedEvent, SessionBackgroundTasksChangedEvent,
// Assistant events
AssistantTurnStartEvent, AssistantIntentEvent, AssistantReasoningEvent, AssistantReasoningDeltaEvent,
AssistantMessageEvent, AssistantMessageDeltaEvent, AssistantStreamingDeltaEvent, AssistantTurnEndEvent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public void setData(ExternalToolRequestedData data) {
@JsonIgnoreProperties(ignoreUnknown = true)
public record ExternalToolRequestedData(@JsonProperty("requestId") String requestId,
@JsonProperty("sessionId") String sessionId, @JsonProperty("toolCallId") String toolCallId,
@JsonProperty("toolName") String toolName, @JsonProperty("arguments") Object arguments) {
@JsonProperty("toolName") String toolName, @JsonProperty("arguments") Object arguments,
@JsonProperty("traceparent") String traceparent, @JsonProperty("tracestate") String tracestate) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/

package com.github.copilot.sdk.events;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

/**
* Event: session.background_tasks_changed
* <p>
* Emitted when the set of background tasks for the session changes.
*
* @since 1.1.0
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public final class SessionBackgroundTasksChangedEvent extends AbstractSessionEvent {

@JsonProperty("data")
private Object data;

@Override
public String getType() {
return "session.background_tasks_changed";
}

public Object getData() {
return data;
}

public void setData(Object data) {
this.data = data;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public class SessionEventParser {
TYPE_MAP.put("session.compaction_complete", SessionCompactionCompleteEvent.class);
TYPE_MAP.put("session.context_changed", SessionContextChangedEvent.class);
TYPE_MAP.put("session.task_complete", SessionTaskCompleteEvent.class);
TYPE_MAP.put("session.tools_updated", SessionToolsUpdatedEvent.class);
TYPE_MAP.put("session.background_tasks_changed", SessionBackgroundTasksChangedEvent.class);
TYPE_MAP.put("user.message", UserMessageEvent.class);
TYPE_MAP.put("pending_messages.modified", PendingMessagesModifiedEvent.class);
TYPE_MAP.put("assistant.turn_start", AssistantTurnStartEvent.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public void setData(SessionModelChangeData data) {

@JsonIgnoreProperties(ignoreUnknown = true)
public record SessionModelChangeData(@JsonProperty("previousModel") String previousModel,
@JsonProperty("newModel") String newModel) {
@JsonProperty("newModel") String newModel,
@JsonProperty("previousReasoningEffort") String previousReasoningEffort,
@JsonProperty("reasoningEffort") String reasoningEffort) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public void setData(SessionResumeData data) {

@JsonIgnoreProperties(ignoreUnknown = true)
public record SessionResumeData(@JsonProperty("resumeTime") OffsetDateTime resumeTime,
@JsonProperty("eventCount") double eventCount) {
@JsonProperty("eventCount") double eventCount, @JsonProperty("selectedModel") String selectedModel,
@JsonProperty("reasoningEffort") String reasoningEffort) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public void setData(SessionStartData data) {
@JsonIgnoreProperties(ignoreUnknown = true)
public record SessionStartData(@JsonProperty("sessionId") String sessionId, @JsonProperty("version") double version,
@JsonProperty("producer") String producer, @JsonProperty("copilotVersion") String copilotVersion,
@JsonProperty("startTime") OffsetDateTime startTime, @JsonProperty("selectedModel") String selectedModel) {
@JsonProperty("startTime") OffsetDateTime startTime, @JsonProperty("selectedModel") String selectedModel,
@JsonProperty("reasoningEffort") String reasoningEffort) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/

package com.github.copilot.sdk.events;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

/**
* Event: session.tools_updated
* <p>
* Emitted when the set of available tools for the session changes.
*
* @since 1.1.0
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public final class SessionToolsUpdatedEvent extends AbstractSessionEvent {

@JsonProperty("data")
private Object data;

@Override
public String getType() {
return "session.tools_updated";
}

public Object getData() {
return data;
}

public void setData(Object data) {
this.data = data;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,17 @@ public class CopilotClientOptions {
private String cliUrl;
private String logLevel = "info";
private boolean autoStart = true;
private boolean autoRestart = true;
/**
* @deprecated AutoRestart has no effect and will be removed in a future
* release.
*/
@Deprecated
private boolean autoRestart = false;
private Map<String, String> environment;
private String gitHubToken;
private Boolean useLoggedInUser;
private Supplier<CompletableFuture<List<ModelInfo>>> onListModels;
private TelemetryConfig telemetry;

/**
* Gets the path to the Copilot CLI executable.
Expand Down Expand Up @@ -236,8 +242,11 @@ public CopilotClientOptions setAutoStart(boolean autoStart) {
/**
* Returns whether the client should automatically restart the server on crash.
*
* @return {@code true} to auto-restart (default), {@code false} otherwise
* @return {@code true} to auto-restart, {@code false} otherwise
* @deprecated AutoRestart has no effect and will be removed in a future
* release.
*/
@Deprecated
public boolean isAutoRestart() {
return autoRestart;
}
Expand All @@ -249,7 +258,10 @@ public boolean isAutoRestart() {
* @param autoRestart
* {@code true} to auto-restart, {@code false} otherwise
* @return this options instance for method chaining
* @deprecated AutoRestart has no effect and will be removed in a future
* release.
*/
@Deprecated
public CopilotClientOptions setAutoRestart(boolean autoRestart) {
this.autoRestart = autoRestart;
return this;
Expand Down Expand Up @@ -378,6 +390,30 @@ public CopilotClientOptions setOnListModels(Supplier<CompletableFuture<List<Mode
return this;
}

/**
* Gets the OpenTelemetry configuration for the CLI server.
*
* @return the telemetry configuration, or {@code null} if not set
*/
public TelemetryConfig getTelemetry() {
return telemetry;
}

/**
* Sets the OpenTelemetry configuration for the CLI server.
* <p>
* When set to a non-{@code null} instance, the CLI server is started with
* OpenTelemetry instrumentation enabled.
*
* @param telemetry
* the telemetry configuration, or {@code null} to disable
* @return this options instance for method chaining
*/
public CopilotClientOptions setTelemetry(TelemetryConfig telemetry) {
this.telemetry = telemetry;
return this;
}

/**
* Creates a shallow clone of this {@code CopilotClientOptions} instance.
* <p>
Expand All @@ -404,6 +440,7 @@ public CopilotClientOptions clone() {
copy.gitHubToken = this.gitHubToken;
copy.useLoggedInUser = this.useLoggedInUser;
copy.onListModels = this.onListModels;
copy.telemetry = this.telemetry;
return copy;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ public final class PermissionRequestResultKind {
public static final PermissionRequestResultKind DENIED_INTERACTIVELY_BY_USER = new PermissionRequestResultKind(
"denied-interactively-by-user");

/**
* Leave the pending permission request unanswered (for extensions that do not
* handle a particular permission).
*
* @since 1.1.0
*/
public static final PermissionRequestResultKind NO_RESULT = new PermissionRequestResultKind("no-result");

private final String value;

/**
Expand Down
Loading
Loading