Skip to content

Commit b6ec90d

Browse files
stephentoubCopilot
andcommitted
Add ContinuePendingWork option to ResumeSessionConfig in all SDKs
Exposes the new continuePendingWork resume option introduced by runtime PR #6099 (suspend/resume support for permission and tool call continuity). When true, the runtime continues any tool calls or permission prompts that were pending when the session was suspended; default false matches existing behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d1826bb commit b6ec90d

12 files changed

Lines changed: 213 additions & 2 deletions

File tree

dotnet/src/Client.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,8 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
632632
Traceparent: traceparent,
633633
Tracestate: tracestate,
634634
ModelCapabilities: config.ModelCapabilities,
635-
GitHubToken: config.GitHubToken);
635+
GitHubToken: config.GitHubToken,
636+
ContinuePendingWork: config.ContinuePendingWork);
636637

637638
var response = await InvokeRpcAsync<ResumeSessionResponse>(
638639
connection.Rpc, "session.resume", [request], cancellationToken);
@@ -1705,7 +1706,8 @@ internal record ResumeSessionRequest(
17051706
string? Traceparent = null,
17061707
string? Tracestate = null,
17071708
ModelCapabilitiesOverride? ModelCapabilities = null,
1708-
string? GitHubToken = null);
1709+
string? GitHubToken = null,
1710+
bool? ContinuePendingWork = null);
17091711

17101712
internal record ResumeSessionResponse(
17111713
string SessionId,

dotnet/src/Types.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2005,6 +2005,7 @@ protected ResumeSessionConfig(ResumeSessionConfig? other)
20052005
DisabledSkills = other.DisabledSkills is not null ? [.. other.DisabledSkills] : null;
20062006
DisableResume = other.DisableResume;
20072007
EnableConfigDiscovery = other.EnableConfigDiscovery;
2008+
ContinuePendingWork = other.ContinuePendingWork;
20082009
ExcludedTools = other.ExcludedTools is not null ? [.. other.ExcludedTools] : null;
20092010
Hooks = other.Hooks;
20102011
InfiniteSessions = other.InfiniteSessions;
@@ -2140,6 +2141,20 @@ protected ResumeSessionConfig(ResumeSessionConfig? other)
21402141
/// </summary>
21412142
public bool DisableResume { get; set; }
21422143

2144+
/// <summary>
2145+
/// When <see langword="true"/>, instructs the runtime to continue any tool calls
2146+
/// or permission prompts that were still pending when the session was last suspended.
2147+
/// When <see langword="false"/> (the default), the runtime treats pending work as
2148+
/// interrupted on resume.
2149+
/// <para>
2150+
/// For permission requests, the runtime re-emits <c>permission.requested</c> so the
2151+
/// registered <see cref="OnPermissionRequest"/> handler can re-prompt; for external
2152+
/// tool calls, the consumer is expected to supply the result via the corresponding
2153+
/// low-level RPC method.
2154+
/// </para>
2155+
/// </summary>
2156+
public bool? ContinuePendingWork { get; set; }
2157+
21432158
/// <summary>
21442159
/// Enable streaming of assistant message and reasoning chunks.
21452160
/// When true, assistant.message_delta and assistant.reasoning_delta events

dotnet/test/CloneTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,4 +303,27 @@ public void ResumeSessionConfig_Clone_PreservesIncludeSubAgentStreamingEventsDef
303303

304304
Assert.True(clone.IncludeSubAgentStreamingEvents);
305305
}
306+
307+
[Fact]
308+
public void ResumeSessionConfig_Clone_CopiesContinuePendingWork()
309+
{
310+
var original = new ResumeSessionConfig
311+
{
312+
ContinuePendingWork = true,
313+
};
314+
315+
var clone = original.Clone();
316+
317+
Assert.True(clone.ContinuePendingWork);
318+
}
319+
320+
[Fact]
321+
public void ResumeSessionConfig_Clone_PreservesContinuePendingWorkDefault()
322+
{
323+
var original = new ResumeSessionConfig();
324+
325+
var clone = original.Clone();
326+
327+
Assert.Null(clone.ContinuePendingWork);
328+
}
306329
}

go/client.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,9 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
779779
if config.DisableResume {
780780
req.DisableResume = Bool(true)
781781
}
782+
if config.ContinuePendingWork {
783+
req.ContinuePendingWork = Bool(true)
784+
}
782785
req.MCPServers = config.MCPServers
783786
req.EnvValueMode = "direct"
784787
req.CustomAgents = config.CustomAgents

go/client_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,36 @@ func TestResumeSessionRequest_RequestElicitation(t *testing.T) {
881881
})
882882
}
883883

884+
func TestResumeSessionRequest_ContinuePendingWork(t *testing.T) {
885+
t.Run("forwards continuePendingWork when true", func(t *testing.T) {
886+
req := resumeSessionRequest{
887+
SessionID: "s1",
888+
ContinuePendingWork: Bool(true),
889+
}
890+
data, err := json.Marshal(req)
891+
if err != nil {
892+
t.Fatalf("Failed to marshal: %v", err)
893+
}
894+
var m map[string]any
895+
if err := json.Unmarshal(data, &m); err != nil {
896+
t.Fatalf("Failed to unmarshal: %v", err)
897+
}
898+
if m["continuePendingWork"] != true {
899+
t.Errorf("Expected continuePendingWork to be true, got %v", m["continuePendingWork"])
900+
}
901+
})
902+
903+
t.Run("omits continuePendingWork when not set", func(t *testing.T) {
904+
req := resumeSessionRequest{SessionID: "s1"}
905+
data, _ := json.Marshal(req)
906+
var m map[string]any
907+
json.Unmarshal(data, &m)
908+
if _, ok := m["continuePendingWork"]; ok {
909+
t.Error("Expected continuePendingWork to be omitted when not set")
910+
}
911+
})
912+
}
913+
884914
func TestCreateSessionRequest_IncludeSubAgentStreamingEvents(t *testing.T) {
885915
t.Run("defaults to true when nil", func(t *testing.T) {
886916
req := createSessionRequest{

go/types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,15 @@ type ResumeSessionConfig struct {
804804
// DisableResume, when true, skips emitting the session.resume event.
805805
// Useful for reconnecting to a session without triggering resume-related side effects.
806806
DisableResume bool
807+
// ContinuePendingWork, when true, instructs the runtime to continue any tool calls
808+
// or permission prompts that were still pending when the session was last suspended.
809+
// When false (the default), the runtime treats pending work as interrupted on resume.
810+
//
811+
// For permission requests, the runtime re-emits permission.requested so the
812+
// registered OnPermissionRequest handler can re-prompt; for external tool calls,
813+
// the consumer is expected to supply the result via the corresponding low-level
814+
// RPC method.
815+
ContinuePendingWork bool
807816
// OnEvent is an optional event handler registered before the session.resume RPC
808817
// is issued, ensuring early events are delivered. See SessionConfig.OnEvent.
809818
OnEvent SessionEventHandler
@@ -1050,6 +1059,7 @@ type resumeSessionRequest struct {
10501059
ConfigDir string `json:"configDir,omitempty"`
10511060
EnableConfigDiscovery *bool `json:"enableConfigDiscovery,omitempty"`
10521061
DisableResume *bool `json:"disableResume,omitempty"`
1062+
ContinuePendingWork *bool `json:"continuePendingWork,omitempty"`
10531063
Streaming *bool `json:"streaming,omitempty"`
10541064
IncludeSubAgentStreamingEvents *bool `json:"includeSubAgentStreamingEvents,omitempty"`
10551065
MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"`

nodejs/src/client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,7 @@ export class CopilotClient {
907907
disabledSkills: config.disabledSkills,
908908
infiniteSessions: config.infiniteSessions,
909909
disableResume: config.disableResume,
910+
continuePendingWork: config.continuePendingWork,
910911
gitHubToken: config.gitHubToken,
911912
});
912913

nodejs/src/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,6 +1421,18 @@ export type ResumeSessionConfig = Pick<
14211421
* @default false
14221422
*/
14231423
disableResume?: boolean;
1424+
/**
1425+
* When true, the runtime continues any tool calls or permission prompts that were
1426+
* still pending when the session was last suspended. When false (the default), the
1427+
* runtime treats pending work as interrupted on resume.
1428+
*
1429+
* For permission requests, the runtime re-emits `permission.requested` so the
1430+
* registered `onPermissionRequest` handler can re-prompt; for external tool calls,
1431+
* the consumer is expected to supply the result via the corresponding low-level
1432+
* RPC method.
1433+
* @default false
1434+
*/
1435+
continuePendingWork?: boolean;
14241436
};
14251437

14261438
/**

nodejs/test/client.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,47 @@ describe("CopilotClient", () => {
166166
spy.mockRestore();
167167
});
168168

169+
it("forwards continuePendingWork in session.resume request", async () => {
170+
const client = new CopilotClient();
171+
await client.start();
172+
onTestFinished(() => client.forceStop());
173+
174+
const session = await client.createSession({ onPermissionRequest: approveAll });
175+
const spy = vi
176+
.spyOn((client as any).connection!, "sendRequest")
177+
.mockImplementation(async (method: string, params: any) => {
178+
if (method === "session.resume") return { sessionId: params.sessionId };
179+
throw new Error(`Unexpected method: ${method}`);
180+
});
181+
await client.resumeSession(session.sessionId, {
182+
onPermissionRequest: approveAll,
183+
continuePendingWork: true,
184+
});
185+
186+
const payload = spy.mock.calls.find((c) => c[0] === "session.resume")![1] as any;
187+
expect(payload.continuePendingWork).toBe(true);
188+
spy.mockRestore();
189+
});
190+
191+
it("omits continuePendingWork from session.resume payload when not specified", async () => {
192+
const client = new CopilotClient();
193+
await client.start();
194+
onTestFinished(() => client.forceStop());
195+
196+
const session = await client.createSession({ onPermissionRequest: approveAll });
197+
const spy = vi
198+
.spyOn((client as any).connection!, "sendRequest")
199+
.mockImplementation(async (method: string, params: any) => {
200+
if (method === "session.resume") return { sessionId: params.sessionId };
201+
throw new Error(`Unexpected method: ${method}`);
202+
});
203+
await client.resumeSession(session.sessionId, { onPermissionRequest: approveAll });
204+
205+
const payload = spy.mock.calls.find((c) => c[0] === "session.resume")![1] as any;
206+
expect(payload.continuePendingWork).toBeUndefined();
207+
spy.mockRestore();
208+
});
209+
169210
it("forwards provider headers in session.create request", async () => {
170211
const client = new CopilotClient();
171212
await client.start();

python/copilot/client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1513,6 +1513,7 @@ async def resume_session(
15131513
on_elicitation_request: ElicitationHandler | None = None,
15141514
create_session_fs_handler: CreateSessionFsHandler | None = None,
15151515
github_token: str | None = None,
1516+
continue_pending_work: bool | None = None,
15161517
) -> CopilotSession:
15171518
"""
15181519
Resume an existing conversation session by its ID.
@@ -1560,6 +1561,10 @@ async def resume_session(
15601561
disabled_skills: Skills to disable.
15611562
infinite_sessions: Infinite session configuration.
15621563
on_event: Callback for session events.
1564+
continue_pending_work: When True, instructs the runtime to continue any
1565+
tool calls or permission prompts that were still pending when the
1566+
session was last suspended. When False (the default), the runtime
1567+
treats pending work as interrupted on resume.
15631568
15641569
Returns:
15651570
A :class:`CopilotSession` instance for the resumed session.
@@ -1667,6 +1672,9 @@ async def resume_session(
16671672
if enable_config_discovery is not None:
16681673
payload["enableConfigDiscovery"] = enable_config_discovery
16691674

1675+
if continue_pending_work is not None:
1676+
payload["continuePendingWork"] = continue_pending_work
1677+
16701678
# TODO: disable_resume is not a keyword arg yet; keeping for future use
16711679
if mcp_servers:
16721680
payload["mcpServers"] = mcp_servers

0 commit comments

Comments
 (0)