Skip to content

Commit 996b614

Browse files
Add cloud session config support (#1306)
* Add cloud session config support Expose the session.create cloud option across SDK clients and forward repository metadata to the runtime. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address cloud session review feedback Remove the manual changelog entry and standardize Rust doc examples on the published crate name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e538795 commit 996b614

17 files changed

Lines changed: 426 additions & 2 deletions

File tree

docs/features/remote-sessions.md

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ session.On((SessionEvent e) =>
9797

9898
<!-- docs-validate: skip -->
9999
```rust
100-
use copilot_sdk::{Client, ClientOptions};
100+
use github_copilot_sdk::{Client, ClientOptions, PermissionRequestResult, SessionConfig};
101101

102102
let client = Client::start(
103103
ClientOptions::new().with_remote(true)
@@ -119,6 +119,92 @@ while let Ok(event) = events.recv().await {
119119

120120
<!-- tabs:end -->
121121

122+
### Cloud sessions
123+
124+
Set the create-session `cloud` option to create a remote session in the cloud instead of a local session. You can include repository metadata to associate the cloud session with a GitHub repository.
125+
126+
<!-- tabs:start -->
127+
128+
#### TypeScript
129+
130+
<!-- docs-validate: skip -->
131+
```typescript
132+
const session = await client.createSession({
133+
onPermissionRequest: async () => ({ allowed: true }),
134+
cloud: {
135+
repository: { owner: "github", name: "copilot-sdk", branch: "main" },
136+
},
137+
});
138+
```
139+
140+
#### Python
141+
142+
<!-- docs-validate: skip -->
143+
```python
144+
from copilot import CloudSessionOptions, CloudSessionRepository
145+
146+
session = await client.create_session(
147+
on_permission_request=PermissionHandler.approve_all,
148+
cloud=CloudSessionOptions(
149+
repository=CloudSessionRepository(
150+
owner="github",
151+
name="copilot-sdk",
152+
branch="main",
153+
)
154+
),
155+
)
156+
```
157+
158+
#### Go
159+
160+
<!-- docs-validate: skip -->
161+
```go
162+
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
163+
Cloud: &copilot.CloudSessionOptions{
164+
Repository: &copilot.CloudSessionRepository{
165+
Owner: "github",
166+
Name: "copilot-sdk",
167+
Branch: "main",
168+
},
169+
},
170+
})
171+
```
172+
173+
#### C#
174+
175+
<!-- docs-validate: skip -->
176+
```csharp
177+
var session = await client.CreateSessionAsync(new SessionConfig
178+
{
179+
Cloud = new CloudSessionOptions
180+
{
181+
Repository = new CloudSessionRepository
182+
{
183+
Owner = "github",
184+
Name = "copilot-sdk",
185+
Branch = "main"
186+
}
187+
}
188+
});
189+
```
190+
191+
#### Rust
192+
193+
<!-- docs-validate: skip -->
194+
```rust
195+
use github_copilot_sdk::{CloudSessionOptions, CloudSessionRepository, SessionConfig};
196+
197+
let session = client.create_session(
198+
SessionConfig::default().with_cloud(
199+
CloudSessionOptions::with_repository(
200+
CloudSessionRepository::new("github", "copilot-sdk").with_branch("main"),
201+
),
202+
),
203+
).await?;
204+
```
205+
206+
<!-- tabs:end -->
207+
122208
### On-demand (per-session toggle)
123209

124210
Use `session.rpc.remote.enable()` to start remote access mid-session, and `session.rpc.remote.disable()` to stop it. This is equivalent to the CLI's `/remote on` and `/remote off` commands.
@@ -199,5 +285,6 @@ The remote URL can be rendered as a QR code for easy mobile access. The SDK prov
199285
## Notes
200286

201287
* The `remote` client option only applies when the SDK spawns the CLI process. It is ignored when connecting to an external server via `cliUrl`.
288+
* The `cloud` session option applies only to new sessions created with `session.create`; it is not used when resuming an existing session.
202289
* If the working directory is not a GitHub repository, remote setup is silently skipped (always-on mode) or returns an error (on-demand mode).
203290
* Remote sessions require authentication. Ensure `gitHubToken` or `useLoggedInUser` is configured.

dotnet/src/Client.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig config, Cance
632632
ModelCapabilities: config.ModelCapabilities,
633633
GitHubToken: config.GitHubToken,
634634
RemoteSession: config.RemoteSession,
635+
Cloud: config.Cloud,
635636
InstructionDirectories: config.InstructionDirectories);
636637

637638
var rpcTimestamp = Stopwatch.GetTimestamp();
@@ -2001,6 +2002,7 @@ internal record CreateSessionRequest(
20012002
ModelCapabilitiesOverride? ModelCapabilities = null,
20022003
string? GitHubToken = null,
20032004
RemoteSessionMode? RemoteSession = null,
2005+
CloudSessionOptions? Cloud = null,
20042006
IList<string>? InstructionDirectories = null);
20052007

20062008
internal record ToolDefinition(

dotnet/src/Types.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1988,6 +1988,32 @@ public class InfiniteSessionConfig
19881988
public double? BufferExhaustionThreshold { get; set; }
19891989
}
19901990

1991+
/// <summary>
1992+
/// GitHub repository metadata to associate with a cloud session.
1993+
/// </summary>
1994+
public class CloudSessionRepository
1995+
{
1996+
/// <summary>Repository owner.</summary>
1997+
public required string Owner { get; set; }
1998+
1999+
/// <summary>Repository name.</summary>
2000+
public required string Name { get; set; }
2001+
2002+
/// <summary>Optional branch name.</summary>
2003+
public string? Branch { get; set; }
2004+
}
2005+
2006+
/// <summary>
2007+
/// Options for creating a remote session in the cloud.
2008+
/// </summary>
2009+
public class CloudSessionOptions
2010+
{
2011+
/// <summary>
2012+
/// Optional GitHub repository metadata to associate with the cloud session.
2013+
/// </summary>
2014+
public CloudSessionRepository? Repository { get; set; }
2015+
}
2016+
19912017
/// <summary>
19922018
/// Configuration options for creating a new Copilot session.
19932019
/// </summary>
@@ -2037,6 +2063,7 @@ protected SessionConfig(SessionConfig? other)
20372063
CreateSessionFsHandler = other.CreateSessionFsHandler;
20382064
GitHubToken = other.GitHubToken;
20392065
RemoteSession = other.RemoteSession;
2066+
Cloud = other.Cloud;
20402067
SessionId = other.SessionId;
20412068
SkillDirectories = other.SkillDirectories is not null ? [.. other.SkillDirectories] : null;
20422069
InstructionDirectories = other.InstructionDirectories is not null ? [.. other.InstructionDirectories] : null;
@@ -2272,6 +2299,12 @@ protected SessionConfig(SessionConfig? other)
22722299
/// </summary>
22732300
public RemoteSessionMode? RemoteSession { get; set; }
22742301

2302+
/// <summary>
2303+
/// Creates a remote session in the cloud instead of a local session.
2304+
/// The optional repository is associated with the cloud session.
2305+
/// </summary>
2306+
public CloudSessionOptions? Cloud { get; set; }
2307+
22752308
/// <summary>
22762309
/// Creates a shallow clone of this <see cref="SessionConfig"/> instance.
22772310
/// </summary>

dotnet/test/Unit/CloneTests.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,15 @@ public void SessionConfig_Clone_CopiesAllProperties()
9696
McpServers = new Dictionary<string, McpServerConfig> { ["server1"] = new McpStdioServerConfig { Command = "echo" } },
9797
CustomAgents = [new CustomAgentConfig { Name = "agent1", Model = "claude-haiku-4.5" }],
9898
Agent = "agent1",
99+
Cloud = new CloudSessionOptions
100+
{
101+
Repository = new CloudSessionRepository
102+
{
103+
Owner = "github",
104+
Name = "copilot-sdk",
105+
Branch = "main"
106+
}
107+
},
99108
DefaultAgent = new DefaultAgentConfig { ExcludedTools = ["hidden-tool"] },
100109
SkillDirectories = ["/skills"],
101110
InstructionDirectories = ["/instructions"],
@@ -121,6 +130,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
121130
Assert.Equal(original.CustomAgents.Count, clone.CustomAgents!.Count);
122131
Assert.Equal(original.CustomAgents[0].Model, clone.CustomAgents[0].Model);
123132
Assert.Equal(original.Agent, clone.Agent);
133+
Assert.Same(original.Cloud, clone.Cloud);
124134
Assert.Equal(original.DefaultAgent!.ExcludedTools, clone.DefaultAgent!.ExcludedTools);
125135
Assert.Equal(original.SkillDirectories, clone.SkillDirectories);
126136
Assert.Equal(original.InstructionDirectories, clone.InstructionDirectories);

dotnet/test/Unit/SerializationTests.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,31 @@ public void CreateSessionRequest_CanSerializeInstructionDirectories_WithSdkOptio
113113
Assert.Equal("C:\\more-instructions", root.GetProperty("instructionDirectories")[1].GetString());
114114
}
115115

116+
[Fact]
117+
public void CreateSessionRequest_CanSerializeCloudOptions_WithSdkOptions()
118+
{
119+
var options = GetSerializerOptions();
120+
var requestType = GetNestedType(typeof(CopilotClient), "CreateSessionRequest");
121+
var request = CreateInternalRequest(
122+
requestType,
123+
("Cloud", new CloudSessionOptions
124+
{
125+
Repository = new CloudSessionRepository
126+
{
127+
Owner = "github",
128+
Name = "copilot-sdk",
129+
Branch = "main"
130+
}
131+
}));
132+
133+
var json = JsonSerializer.Serialize(request, requestType, options);
134+
using var document = JsonDocument.Parse(json);
135+
var repository = document.RootElement.GetProperty("cloud").GetProperty("repository");
136+
Assert.Equal("github", repository.GetProperty("owner").GetString());
137+
Assert.Equal("copilot-sdk", repository.GetProperty("name").GetString());
138+
Assert.Equal("main", repository.GetProperty("branch").GetString());
139+
}
140+
116141
[Fact]
117142
public void CreateSessionRequest_CanSerializeModeRequestFlags_WithSdkOptions()
118143
{

go/client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
646646
req.InfiniteSessions = config.InfiniteSessions
647647
req.GitHubToken = config.GitHubToken
648648
req.RemoteSession = config.RemoteSession
649+
req.Cloud = config.Cloud
649650

650651
if len(config.Commands) > 0 {
651652
cmds := make([]wireCommand, 0, len(config.Commands))

go/client_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,55 @@ func TestCreateSessionRequest_Commands(t *testing.T) {
865865
})
866866
}
867867

868+
func TestCreateSessionRequest_Cloud(t *testing.T) {
869+
t.Run("forwards cloud options in session.create RPC", func(t *testing.T) {
870+
req := createSessionRequest{
871+
Cloud: &CloudSessionOptions{
872+
Repository: &CloudSessionRepository{
873+
Owner: "github",
874+
Name: "copilot-sdk",
875+
Branch: "main",
876+
},
877+
},
878+
}
879+
data, err := json.Marshal(req)
880+
if err != nil {
881+
t.Fatalf("Failed to marshal: %v", err)
882+
}
883+
var m map[string]any
884+
if err := json.Unmarshal(data, &m); err != nil {
885+
t.Fatalf("Failed to unmarshal: %v", err)
886+
}
887+
cloud, ok := m["cloud"].(map[string]any)
888+
if !ok {
889+
t.Fatalf("Expected cloud to be an object, got %T", m["cloud"])
890+
}
891+
repository, ok := cloud["repository"].(map[string]any)
892+
if !ok {
893+
t.Fatalf("Expected cloud.repository to be an object, got %T", cloud["repository"])
894+
}
895+
if repository["owner"] != "github" {
896+
t.Errorf("Expected owner 'github', got %v", repository["owner"])
897+
}
898+
if repository["name"] != "copilot-sdk" {
899+
t.Errorf("Expected name 'copilot-sdk', got %v", repository["name"])
900+
}
901+
if repository["branch"] != "main" {
902+
t.Errorf("Expected branch 'main', got %v", repository["branch"])
903+
}
904+
})
905+
906+
t.Run("omits cloud from JSON when unset", func(t *testing.T) {
907+
req := createSessionRequest{}
908+
data, _ := json.Marshal(req)
909+
var m map[string]any
910+
json.Unmarshal(data, &m)
911+
if _, ok := m["cloud"]; ok {
912+
t.Error("Expected cloud to be omitted when unset")
913+
}
914+
})
915+
}
916+
868917
func TestResumeSessionRequest_Commands(t *testing.T) {
869918
t.Run("forwards commands in session.resume RPC", func(t *testing.T) {
870919
req := resumeSessionRequest{

go/types.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,18 @@ type ClientOptions struct {
9898
Remote bool
9999
}
100100

101+
// CloudSessionRepository is GitHub repository metadata associated with a cloud session.
102+
type CloudSessionRepository struct {
103+
Owner string `json:"owner"`
104+
Name string `json:"name"`
105+
Branch string `json:"branch,omitempty"`
106+
}
107+
108+
// CloudSessionOptions configures creation of a remote session in the cloud.
109+
type CloudSessionOptions struct {
110+
Repository *CloudSessionRepository `json:"repository,omitempty"`
111+
}
112+
101113
// TelemetryConfig configures OpenTelemetry integration for the Copilot CLI process.
102114
type TelemetryConfig struct {
103115
// OTLPEndpoint is the OTLP HTTP endpoint URL for trace/metric export.
@@ -689,6 +701,9 @@ type SessionConfig struct {
689701
// - "export" — export session events to GitHub without enabling remote steering
690702
// - "on" — export to GitHub AND enable remote steering
691703
RemoteSession rpc.RemoteSessionMode
704+
// Cloud creates a remote session in the cloud instead of a local session.
705+
// The optional repository is associated with the cloud session.
706+
Cloud *CloudSessionOptions
692707
}
693708
type Tool struct {
694709
Name string `json:"name"`
@@ -1155,6 +1170,7 @@ type createSessionRequest struct {
11551170
RequestElicitation *bool `json:"requestElicitation,omitempty"`
11561171
GitHubToken string `json:"gitHubToken,omitempty"`
11571172
RemoteSession rpc.RemoteSessionMode `json:"remoteSession,omitempty"`
1173+
Cloud *CloudSessionOptions `json:"cloud,omitempty"`
11581174
Traceparent string `json:"traceparent,omitempty"`
11591175
Tracestate string `json:"tracestate,omitempty"`
11601176
}

nodejs/src/client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,7 @@ export class CopilotClient {
836836
infiniteSessions: config.infiniteSessions,
837837
gitHubToken: config.gitHubToken,
838838
remoteSession: config.remoteSession,
839+
cloud: config.cloud,
839840
});
840841

841842
const { workspacePath, capabilities } = response as {

nodejs/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export type {
2121
CommandContext,
2222
CommandDefinition,
2323
CommandHandler,
24+
CloudSessionOptions,
25+
CloudSessionRepository,
2426
AutoModeSwitchHandler,
2527
AutoModeSwitchRequest,
2628
AutoModeSwitchResponse,

0 commit comments

Comments
 (0)