Skip to content
Merged
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
4 changes: 4 additions & 0 deletions dotnet/src/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig config, Cance
Tracestate: tracestate,
ModelCapabilities: config.ModelCapabilities,
GitHubToken: config.GitHubToken,
RemoteSession: config.RemoteSession,
InstructionDirectories: config.InstructionDirectories);

var rpcTimestamp = Stopwatch.GetTimestamp();
Expand Down Expand Up @@ -786,6 +787,7 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
Tracestate: tracestate,
ModelCapabilities: config.ModelCapabilities,
GitHubToken: config.GitHubToken,
RemoteSession: config.RemoteSession,
ContinuePendingWork: config.ContinuePendingWork,
InstructionDirectories: config.InstructionDirectories);

Expand Down Expand Up @@ -1981,6 +1983,7 @@ internal record CreateSessionRequest(
string? Tracestate = null,
ModelCapabilitiesOverride? ModelCapabilities = null,
string? GitHubToken = null,
RemoteSessionMode? RemoteSession = null,
IList<string>? InstructionDirectories = null);

internal record ToolDefinition(
Expand Down Expand Up @@ -2041,6 +2044,7 @@ internal record ResumeSessionRequest(
string? Tracestate = null,
ModelCapabilitiesOverride? ModelCapabilities = null,
string? GitHubToken = null,
RemoteSessionMode? RemoteSession = null,
bool? ContinuePendingWork = null,
IList<string>? InstructionDirectories = null);

Expand Down
18 changes: 18 additions & 0 deletions dotnet/src/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2028,6 +2028,7 @@ protected SessionConfig(SessionConfig? other)
ReasoningEffort = other.ReasoningEffort;
CreateSessionFsHandler = other.CreateSessionFsHandler;
GitHubToken = other.GitHubToken;
RemoteSession = other.RemoteSession;
SessionId = other.SessionId;
SkillDirectories = other.SkillDirectories is not null ? [.. other.SkillDirectories] : null;
InstructionDirectories = other.InstructionDirectories is not null ? [.. other.InstructionDirectories] : null;
Expand Down Expand Up @@ -2253,6 +2254,16 @@ protected SessionConfig(SessionConfig? other)
/// </summary>
public string? GitHubToken { get; set; }

/// <summary>
/// Per-session remote behavior control:
/// <list type="bullet">
/// <item><description><c>"off"</c> — local only, no remote export (default)</description></item>
/// <item><description><c>"export"</c> — export session events to GitHub without enabling remote steering</description></item>
/// <item><description><c>"on"</c> — export to GitHub AND enable remote steering</description></item>
/// </list>
/// </summary>
public RemoteSessionMode? RemoteSession { get; set; }

/// <summary>
/// Creates a shallow clone of this <see cref="SessionConfig"/> instance.
/// </summary>
Expand Down Expand Up @@ -2319,6 +2330,7 @@ protected ResumeSessionConfig(ResumeSessionConfig? other)
ReasoningEffort = other.ReasoningEffort;
CreateSessionFsHandler = other.CreateSessionFsHandler;
GitHubToken = other.GitHubToken;
RemoteSession = other.RemoteSession;
SkillDirectories = other.SkillDirectories is not null ? [.. other.SkillDirectories] : null;
InstructionDirectories = other.InstructionDirectories is not null ? [.. other.InstructionDirectories] : null;
Streaming = other.Streaming;
Expand Down Expand Up @@ -2555,6 +2567,12 @@ protected ResumeSessionConfig(ResumeSessionConfig? other)
/// </summary>
public string? GitHubToken { get; set; }

/// <summary>
/// Per-session remote behavior control.
/// See <see cref="SessionConfig.RemoteSession"/> for details.
/// </summary>
public RemoteSessionMode? RemoteSession { get; set; }

/// <summary>
/// Creates a shallow clone of this <see cref="ResumeSessionConfig"/> instance.
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions go/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
req.DisabledSkills = config.DisabledSkills
req.InfiniteSessions = config.InfiniteSessions
req.GitHubToken = config.GitHubToken
req.RemoteSession = config.RemoteSession

if len(config.Commands) > 0 {
cmds := make([]wireCommand, 0, len(config.Commands))
Expand Down Expand Up @@ -848,6 +849,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
req.DisabledSkills = config.DisabledSkills
req.InfiniteSessions = config.InfiniteSessions
req.GitHubToken = config.GitHubToken
req.RemoteSession = config.RemoteSession
req.RequestPermission = Bool(true)

if len(config.Commands) > 0 {
Expand Down
10 changes: 10 additions & 0 deletions go/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,11 @@ type SessionConfig struct {
// When provided, the session authenticates as the token's owner instead of
// using the global client-level auth.
GitHubToken string `json:"-"`
// RemoteSession controls per-session remote behavior:
// - "off" — local only, no remote export (default)
// - "export" — export session events to GitHub without enabling remote steering
// - "on" — export to GitHub AND enable remote steering
RemoteSession rpc.RemoteSessionMode
}
type Tool struct {
Name string `json:"name"`
Expand Down Expand Up @@ -891,6 +896,9 @@ type ResumeSessionConfig struct {
// When provided, the session authenticates as the token's owner instead of
// using the global client-level auth.
GitHubToken string `json:"-"`
// RemoteSession controls per-session remote behavior.
// See SessionConfig.RemoteSession for details.
RemoteSession rpc.RemoteSessionMode
// DisableResume, when true, skips emitting the session.resume event.
// Useful for reconnecting to a session without triggering resume-related side effects.
DisableResume bool
Expand Down Expand Up @@ -1142,6 +1150,7 @@ type createSessionRequest struct {
Commands []wireCommand `json:"commands,omitempty"`
RequestElicitation *bool `json:"requestElicitation,omitempty"`
GitHubToken string `json:"gitHubToken,omitempty"`
RemoteSession rpc.RemoteSessionMode `json:"remoteSession,omitempty"`
Traceparent string `json:"traceparent,omitempty"`
Tracestate string `json:"tracestate,omitempty"`
}
Expand Down Expand Up @@ -1196,6 +1205,7 @@ type resumeSessionRequest struct {
Commands []wireCommand `json:"commands,omitempty"`
RequestElicitation *bool `json:"requestElicitation,omitempty"`
GitHubToken string `json:"gitHubToken,omitempty"`
RemoteSession rpc.RemoteSessionMode `json:"remoteSession,omitempty"`
Traceparent string `json:"traceparent,omitempty"`
Tracestate string `json:"tracestate,omitempty"`
}
Expand Down
2 changes: 2 additions & 0 deletions nodejs/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,7 @@ export class CopilotClient {
disabledSkills: config.disabledSkills,
infiniteSessions: config.infiniteSessions,
gitHubToken: config.gitHubToken,
remoteSession: config.remoteSession,
});

const { workspacePath, capabilities } = response as {
Expand Down Expand Up @@ -990,6 +991,7 @@ export class CopilotClient {
disableResume: config.disableResume,
continuePendingWork: config.continuePendingWork,
gitHubToken: config.gitHubToken,
remoteSession: config.remoteSession,
});

const { workspacePath, capabilities } = response as {
Expand Down
1 change: 1 addition & 0 deletions nodejs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export type {
PermissionRequest,
PermissionRequestResult,
ProviderConfig,
RemoteSessionMode,
ResumeSessionConfig,
SectionOverride,
SectionOverrideAction,
Expand Down
11 changes: 11 additions & 0 deletions nodejs/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import type { SessionFsProvider } from "./sessionFsProvider.js";
import type { SessionEvent as GeneratedSessionEvent } from "./generated/session-events.js";
import type { CopilotSession } from "./session.js";
import type { RemoteSessionMode } from "./generated/rpc.js";
export type { RemoteSessionMode } from "./generated/rpc.js";
export type SessionEvent = GeneratedSessionEvent;
export type { SessionFsProvider } from "./sessionFsProvider.js";
export { createSessionFsAdapter } from "./sessionFsProvider.js";
Expand Down Expand Up @@ -1477,6 +1479,14 @@ export interface SessionConfig {
*/
gitHubToken?: string;

/**
* Per-session remote behavior control:
* - `"off"` — local only, no remote export (default)
* - `"export"` — export session events to GitHub without enabling remote steering
* - `"on"` — export to GitHub AND enable remote steering
*/
remoteSession?: RemoteSessionMode;

/**
* Optional event handler that is registered on the session before the
* session.create RPC is issued. This guarantees that early events emitted
Expand Down Expand Up @@ -1531,6 +1541,7 @@ export type ResumeSessionConfig = Pick<
| "disabledSkills"
| "infiniteSessions"
| "gitHubToken"
| "remoteSession"
| "onEvent"
| "createSessionFsHandler"
> & {
Expand Down
2 changes: 2 additions & 0 deletions python/copilot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
ModelLimitsOverride,
ModelSupportsOverride,
ModelVisionLimitsOverride,
RemoteSessionMode,
SubprocessConfig,
)
from .session import (
Expand Down Expand Up @@ -67,6 +68,7 @@
"ModelSupportsOverride",
"ModelVisionLimitsOverride",
"ProviderConfig",
"RemoteSessionMode",
"SessionCapabilities",
"SessionFsConfig",
"SessionFsFileInfo",
Expand Down
11 changes: 11 additions & 0 deletions python/copilot/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from .generated.rpc import (
ClientSessionApiHandlers,
ConnectRequest,
RemoteSessionMode,
Comment thread
devm33 marked this conversation as resolved.
ServerRpc,
_InternalServerRpc,
register_client_session_api_handlers,
Expand Down Expand Up @@ -1326,6 +1327,7 @@ async def create_session(
on_auto_mode_switch: AutoModeSwitchHandler | None = None,
create_session_fs_handler: CreateSessionFsHandler | None = None,
github_token: str | None = None,
remote_session: RemoteSessionMode | None = None,
) -> CopilotSession:
"""
Create a new conversation session with the Copilot CLI.
Expand Down Expand Up @@ -1479,6 +1481,10 @@ async def create_session(
if github_token is not None:
payload["gitHubToken"] = github_token

# Add remote session mode if provided
if remote_session is not None:
payload["remoteSession"] = remote_session.value

# Add working directory if provided
if working_directory:
payload["workingDirectory"] = working_directory
Expand Down Expand Up @@ -1686,6 +1692,7 @@ async def resume_session(
on_auto_mode_switch: AutoModeSwitchHandler | None = None,
create_session_fs_handler: CreateSessionFsHandler | None = None,
github_token: str | None = None,
remote_session: RemoteSessionMode | None = None,
continue_pending_work: bool | None = None,
) -> CopilotSession:
"""
Expand Down Expand Up @@ -1857,6 +1864,10 @@ async def resume_session(
if github_token is not None:
payload["gitHubToken"] = github_token

# Add remote session mode if provided
if remote_session is not None:
payload["remoteSession"] = remote_session.value

if working_directory:
payload["workingDirectory"] = working_directory
if config_dir:
Expand Down
33 changes: 33 additions & 0 deletions rust/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,13 @@ pub struct SessionConfig {
/// quota checks for *this session*.
#[serde(rename = "gitHubToken", skip_serializing_if = "Option::is_none")]
pub github_token: Option<String>,
/// Per-session remote behavior control:
/// - `Off` — local only, no remote export (default)
/// - `Export` — export session events to GitHub without
/// enabling remote steering
/// - `On` — export to GitHub AND enable remote steering
#[serde(skip_serializing_if = "Option::is_none")]
pub remote_session: Option<crate::generated::api_types::RemoteSessionMode>,
Comment thread
devm33 marked this conversation as resolved.
Comment thread
devm33 marked this conversation as resolved.
/// Forward sub-agent streaming events to this connection. When false,
/// only non-streaming sub-agent events and `subagent.*` lifecycle events
/// are delivered. Defaults to true on the CLI.
Expand Down Expand Up @@ -1152,6 +1159,7 @@ impl std::fmt::Debug for SessionConfig {
"github_token",
&self.github_token.as_ref().map(|_| "<redacted>"),
)
.field("remote_session", &self.remote_session)
.field(
"include_sub_agent_streaming_events",
&self.include_sub_agent_streaming_events,
Expand Down Expand Up @@ -1211,6 +1219,7 @@ impl Default for SessionConfig {
config_dir: None,
working_directory: None,
github_token: None,
remote_session: None,
include_sub_agent_streaming_events: None,
commands: None,
session_fs_provider: None,
Expand Down Expand Up @@ -1528,6 +1537,15 @@ impl SessionConfig {
self.include_sub_agent_streaming_events = Some(include);
self
}

/// Set per-session remote behavior.
pub fn with_remote_session(
mut self,
mode: crate::generated::api_types::RemoteSessionMode,
) -> Self {
self.remote_session = Some(mode);
self
}
}

/// Configuration for resuming an existing session via the `session.resume` RPC.
Expand Down Expand Up @@ -1639,6 +1657,10 @@ pub struct ResumeSessionConfig {
/// [`SessionConfig::github_token`].
#[serde(rename = "gitHubToken", skip_serializing_if = "Option::is_none")]
pub github_token: Option<String>,
/// Per-session remote behavior control on resume. See
/// [`SessionConfig::remote_session`].
#[serde(skip_serializing_if = "Option::is_none")]
Comment thread
devm33 marked this conversation as resolved.
pub remote_session: Option<crate::generated::api_types::RemoteSessionMode>,
/// Forward sub-agent streaming events to this connection on resume.
#[serde(skip_serializing_if = "Option::is_none")]
pub include_sub_agent_streaming_events: Option<bool>,
Expand Down Expand Up @@ -1712,6 +1734,7 @@ impl std::fmt::Debug for ResumeSessionConfig {
"github_token",
&self.github_token.as_ref().map(|_| "<redacted>"),
)
.field("remote_session", &self.remote_session)
.field(
"include_sub_agent_streaming_events",
&self.include_sub_agent_streaming_events,
Expand Down Expand Up @@ -1770,6 +1793,7 @@ impl ResumeSessionConfig {
config_dir: None,
working_directory: None,
github_token: None,
remote_session: None,
include_sub_agent_streaming_events: None,
commands: None,
session_fs_provider: None,
Expand Down Expand Up @@ -2054,6 +2078,15 @@ impl ResumeSessionConfig {
self
}

/// Set per-session remote behavior on resume.
pub fn with_remote_session(
mut self,
mode: crate::generated::api_types::RemoteSessionMode,
) -> Self {
self.remote_session = Some(mode);
self
}

/// Force-fail resume if the session does not exist on disk, instead
/// of silently starting a new session.
pub fn with_disable_resume(mut self, disable: bool) -> Self {
Expand Down
11 changes: 11 additions & 0 deletions rust/tests/session_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2597,6 +2597,8 @@ fn session_config_serializes_bucket_b_fields() {
cfg.github_token = Some("ghs_secret".to_string());
cfg.include_sub_agent_streaming_events = Some(false);
cfg.enable_session_telemetry = Some(false);
cfg.remote_session =
Some(github_copilot_sdk::generated::api_types::RemoteSessionMode::Export);
cfg
};
let json = serde_json::to_value(&cfg).unwrap();
Expand All @@ -2606,6 +2608,7 @@ fn session_config_serializes_bucket_b_fields() {
assert_eq!(json["gitHubToken"], "ghs_secret");
assert_eq!(json["includeSubAgentStreamingEvents"], false);
assert_eq!(json["enableSessionTelemetry"], false);
assert_eq!(json["remoteSession"], "export");

// Debug never leaks the token.
let debug = format!("{cfg:?}");
Expand All @@ -2617,6 +2620,7 @@ fn session_config_serializes_bucket_b_fields() {
assert!(empty.get("sessionId").is_none());
assert!(empty.get("gitHubToken").is_none());
assert!(empty.get("enableSessionTelemetry").is_none());
assert!(empty.get("remoteSession").is_none());
}

#[test]
Expand All @@ -2631,13 +2635,20 @@ fn resume_session_config_serializes_bucket_b_fields() {
cfg.github_token = Some("ghs_secret".to_string());
cfg.include_sub_agent_streaming_events = Some(true);
cfg.enable_session_telemetry = Some(false);
cfg.remote_session = Some(github_copilot_sdk::generated::api_types::RemoteSessionMode::On);
let json = serde_json::to_value(&cfg).unwrap();
assert_eq!(json["sessionId"], "sess-1");
assert_eq!(json["workingDirectory"], "/tmp/work");
assert_eq!(json["configDir"], "/tmp/cfg");
assert_eq!(json["gitHubToken"], "ghs_secret");
assert_eq!(json["includeSubAgentStreamingEvents"], true);
assert_eq!(json["enableSessionTelemetry"], false);
assert_eq!(json["remoteSession"], "on");

// Unset remote_session is omitted on the wire.
let empty = ResumeSessionConfig::new(SessionId::from("sess-2"));
let empty_json = serde_json::to_value(&empty).unwrap();
assert!(empty_json.get("remoteSession").is_none());

let debug = format!("{cfg:?}");
assert!(!debug.contains("ghs_secret"), "leaked token: {debug}");
Expand Down
Loading