Skip to content

Commit 8ea4506

Browse files
feat: add sessionIdleTimeoutSeconds to Go, Python, and .NET SDKs
Add the session idle timeout option across all remaining SDK languages, consistent with the Node.js implementation and the runtime CLI's --session-idle-timeout flag. - Go: SessionIdleTimeoutSeconds int on ClientOptions - Python: session_idle_timeout_seconds on SubprocessConfig - .NET: SessionIdleTimeoutSeconds int? on CopilotClientOptions Each SDK passes --session-idle-timeout <seconds> to the CLI when the value is positive, and omits it otherwise (disabled by default). Includes unit tests for all three languages and updates the .NET clone test to cover the new property. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 9c32956 commit 8ea4506

9 files changed

Lines changed: 105 additions & 0 deletions

File tree

dotnet/src/Client.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,6 +1190,11 @@ private async Task VerifyProtocolVersionAsync(Connection connection, Cancellatio
11901190
args.Add("--no-auto-login");
11911191
}
11921192

1193+
if (options.SessionIdleTimeoutSeconds is > 0)
1194+
{
1195+
args.AddRange(["--session-idle-timeout", options.SessionIdleTimeoutSeconds.Value.ToString(CultureInfo.InvariantCulture)]);
1196+
}
1197+
11931198
var (fileName, processArgs) = ResolveCliCommand(cliPath, args);
11941199

11951200
var startInfo = new ProcessStartInfo

dotnet/src/Types.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ protected CopilotClientOptions(CopilotClientOptions? other)
6969
UseStdio = other.UseStdio;
7070
OnListModels = other.OnListModels;
7171
SessionFs = other.SessionFs;
72+
SessionIdleTimeoutSeconds = other.SessionIdleTimeoutSeconds;
7273
}
7374

7475
/// <summary>
@@ -165,6 +166,16 @@ public string? GithubToken
165166
/// </summary>
166167
public TelemetryConfig? Telemetry { get; set; }
167168

169+
/// <summary>
170+
/// Server-wide idle timeout for sessions in seconds.
171+
/// Sessions without activity for this duration are automatically cleaned up.
172+
/// Set to <c>0</c> or leave as <see langword="null"/> to disable (sessions live indefinitely).
173+
/// Minimum value: 300 (5 minutes).
174+
/// This option is only used when the SDK spawns the CLI process; it is ignored
175+
/// when connecting to an external server via <see cref="CliUrl"/>.
176+
/// </summary>
177+
public int? SessionIdleTimeoutSeconds { get; set; }
178+
168179
/// <summary>
169180
/// Creates a shallow clone of this <see cref="CopilotClientOptions"/> instance.
170181
/// </summary>

dotnet/test/ClientTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,25 @@ public void Should_Throw_When_UseLoggedInUser_Used_With_CliUrl()
216216
});
217217
}
218218

219+
[Fact]
220+
public void Should_Default_SessionIdleTimeoutSeconds_To_Null()
221+
{
222+
var options = new CopilotClientOptions();
223+
224+
Assert.Null(options.SessionIdleTimeoutSeconds);
225+
}
226+
227+
[Fact]
228+
public void Should_Accept_SessionIdleTimeoutSeconds_Option()
229+
{
230+
var options = new CopilotClientOptions
231+
{
232+
SessionIdleTimeoutSeconds = 600
233+
};
234+
235+
Assert.Equal(600, options.SessionIdleTimeoutSeconds);
236+
}
237+
219238
[Fact]
220239
public async Task Should_Not_Throw_When_Disposing_Session_After_Stopping_Client()
221240
{

dotnet/test/CloneTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public void CopilotClientOptions_Clone_CopiesAllProperties()
2626
Environment = new Dictionary<string, string> { ["KEY"] = "value" },
2727
GitHubToken = "ghp_test",
2828
UseLoggedInUser = false,
29+
SessionIdleTimeoutSeconds = 600,
2930
};
3031

3132
var clone = original.Clone();
@@ -42,6 +43,7 @@ public void CopilotClientOptions_Clone_CopiesAllProperties()
4243
Assert.Equal(original.Environment, clone.Environment);
4344
Assert.Equal(original.GitHubToken, clone.GitHubToken);
4445
Assert.Equal(original.UseLoggedInUser, clone.UseLoggedInUser);
46+
Assert.Equal(original.SessionIdleTimeoutSeconds, clone.SessionIdleTimeoutSeconds);
4547
}
4648

4749
[Fact]

go/client.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,12 @@ func NewClient(options *ClientOptions) *Client {
215215
sessionFs := *options.SessionFs
216216
opts.SessionFs = &sessionFs
217217
}
218+
if options.Telemetry != nil {
219+
opts.Telemetry = options.Telemetry
220+
}
221+
if options.SessionIdleTimeoutSeconds > 0 {
222+
opts.SessionIdleTimeoutSeconds = options.SessionIdleTimeoutSeconds
223+
}
218224
}
219225

220226
// Default Env to current environment if not set
@@ -1378,6 +1384,10 @@ func (c *Client) startCLIServer(ctx context.Context) error {
13781384
args = append(args, "--no-auto-login")
13791385
}
13801386

1387+
if c.options.SessionIdleTimeoutSeconds > 0 {
1388+
args = append(args, "--session-idle-timeout", strconv.Itoa(c.options.SessionIdleTimeoutSeconds))
1389+
}
1390+
13811391
// If CLIPath is a .js file, run it with node
13821392
// Note we can't rely on the shebang as Windows doesn't support it
13831393
command := cliPath

go/client_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,26 @@ func TestClient_EnvOptions(t *testing.T) {
391391
})
392392
}
393393

394+
func TestClient_SessionIdleTimeoutSeconds(t *testing.T) {
395+
t.Run("should store SessionIdleTimeoutSeconds option", func(t *testing.T) {
396+
client := NewClient(&ClientOptions{
397+
SessionIdleTimeoutSeconds: 600,
398+
})
399+
400+
if client.options.SessionIdleTimeoutSeconds != 600 {
401+
t.Errorf("Expected SessionIdleTimeoutSeconds to be 600, got %d", client.options.SessionIdleTimeoutSeconds)
402+
}
403+
})
404+
405+
t.Run("should default SessionIdleTimeoutSeconds to zero", func(t *testing.T) {
406+
client := NewClient(&ClientOptions{})
407+
408+
if client.options.SessionIdleTimeoutSeconds != 0 {
409+
t.Errorf("Expected SessionIdleTimeoutSeconds to be 0, got %d", client.options.SessionIdleTimeoutSeconds)
410+
}
411+
})
412+
}
413+
394414
func findCLIPathForTest() string {
395415
abs, _ := filepath.Abs("../nodejs/node_modules/@github/copilot/index.js")
396416
if fileExistsForTest(abs) {

go/types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ type ClientOptions struct {
7171
// When non-nil, COPILOT_OTEL_ENABLED=true is set and any populated fields
7272
// are mapped to the corresponding environment variables.
7373
Telemetry *TelemetryConfig
74+
// SessionIdleTimeoutSeconds configures the server-wide session idle timeout in seconds.
75+
// Sessions without activity for this duration are automatically cleaned up.
76+
// Set to 0 or leave unset to disable (sessions live indefinitely).
77+
// Minimum value: 300 (5 minutes).
78+
// This option is only used when the SDK spawns the CLI process; it is ignored
79+
// when connecting to an external server via CLIUrl.
80+
SessionIdleTimeoutSeconds int
7481
}
7582

7683
// TelemetryConfig configures OpenTelemetry integration for the Copilot CLI process.

python/copilot/client.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,14 @@ class SubprocessConfig:
150150
session_fs: SessionFsConfig | None = None
151151
"""Connection-level session filesystem provider configuration."""
152152

153+
session_idle_timeout_seconds: int | None = None
154+
"""Server-wide session idle timeout in seconds.
155+
156+
Sessions without activity for this duration are automatically cleaned up.
157+
Set to ``None`` or ``0`` to disable (sessions live indefinitely).
158+
Minimum value: 300 (5 minutes).
159+
"""
160+
153161

154162
@dataclass
155163
class ExternalServerConfig:
@@ -2261,6 +2269,9 @@ async def _start_cli_server(self) -> None:
22612269
if not cfg.use_logged_in_user:
22622270
args.append("--no-auto-login")
22632271

2272+
if cfg.session_idle_timeout_seconds is not None and cfg.session_idle_timeout_seconds > 0:
2273+
args.extend(["--session-idle-timeout", str(cfg.session_idle_timeout_seconds)])
2274+
22642275
# If cli_path is a .js file, run it with node
22652276
# Note that we can't rely on the shebang as Windows doesn't support it
22662277
if cli_path.endswith(".js"):

python/test_client.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,26 @@ def test_explicit_use_logged_in_user_false_without_token(self):
204204
assert client._config.use_logged_in_user is False
205205

206206

207+
class TestSessionIdleTimeoutSeconds:
208+
def test_accepts_session_idle_timeout_seconds(self):
209+
client = CopilotClient(
210+
SubprocessConfig(
211+
cli_path=CLI_PATH,
212+
session_idle_timeout_seconds=600,
213+
log_level="error",
214+
)
215+
)
216+
assert isinstance(client._config, SubprocessConfig)
217+
assert client._config.session_idle_timeout_seconds == 600
218+
219+
def test_default_session_idle_timeout_seconds_is_none(self):
220+
client = CopilotClient(
221+
SubprocessConfig(cli_path=CLI_PATH, log_level="error")
222+
)
223+
assert isinstance(client._config, SubprocessConfig)
224+
assert client._config.session_idle_timeout_seconds is None
225+
226+
207227
class TestOverridesBuiltInTool:
208228
@pytest.mark.asyncio
209229
async def test_overrides_built_in_tool_sent_in_tool_definition(self):

0 commit comments

Comments
 (0)