Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Test updates
  • Loading branch information
SteveSandersonMS committed Feb 24, 2026
commit bc990b472c3309402d7848249fa403aa19410c30
6 changes: 3 additions & 3 deletions dotnet/src/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig config, Cance
config.OnUserInputRequest != null ? true : null,
hasHooks ? true : null,
config.WorkingDirectory,
config.Streaming == true ? true : null,
config.Streaming is true ? true : null,
config.McpServers,
"direct",
config.CustomAgents,
Expand Down Expand Up @@ -487,8 +487,8 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
hasHooks ? true : null,
config.WorkingDirectory,
config.ConfigDir,
config.DisableResume == true ? true : null,
config.Streaming == true ? true : null,
config.DisableResume is true ? true : null,
config.Streaming is true ? true : null,
config.McpServers,
"direct",
config.CustomAgents,
Expand Down
21 changes: 14 additions & 7 deletions dotnet/test/PermissionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public async Task Should_Deny_Permission_When_Handler_Returns_Denied()
{
return Task.FromResult(new PermissionRequestResult
{
Kind = "denied-interactively-by-user"
Kind = "denied-no-approval-rule-and-could-not-request-from-user"
});
}
});
Expand All @@ -71,9 +71,13 @@ await session.SendAsync(new MessageOptions
}

[Fact]
public async Task Should_Deny_Tool_Operations_By_Default_When_No_Handler_Is_Provided()
public async Task Should_Deny_Tool_Operations_When_Handler_Explicitly_Denies()
{
var session = await CreateSessionAsync(new SessionConfig());
var session = await CreateSessionAsync(new SessionConfig
{
OnPermissionRequest = (_, _) =>
Task.FromResult(new PermissionRequestResult { Kind = "denied-no-approval-rule-and-could-not-request-from-user" })
});
var permissionDenied = false;

session.On(evt =>
Expand All @@ -95,9 +99,8 @@ await session.SendAndWaitAsync(new MessageOptions
}
Comment thread
SteveSandersonMS marked this conversation as resolved.
Outdated

[Fact]
public async Task Should_Work_Without_Permission_Handler__Default_Behavior_()
public async Task Should_Work_With_Approve_All_Permission_Handler()
{
// Create session without permission handler
var session = await CreateSessionAsync(new SessionConfig());

await session.SendAsync(new MessageOptions
Expand Down Expand Up @@ -186,7 +189,7 @@ await session.SendAsync(new MessageOptions
}

[Fact]
public async Task Should_Deny_Tool_Operations_By_Default_When_No_Handler_Is_Provided_After_Resume()
public async Task Should_Deny_Tool_Operations_When_Handler_Explicitly_Denies_After_Resume()
{
var session1 = await CreateSessionAsync(new SessionConfig
{
Expand All @@ -195,7 +198,11 @@ public async Task Should_Deny_Tool_Operations_By_Default_When_No_Handler_Is_Prov
var sessionId = session1.SessionId;
await session1.SendAndWaitAsync(new MessageOptions { Prompt = "What is 1+1?" });

var session2 = await ResumeSessionAsync(sessionId);
var session2 = await ResumeSessionAsync(sessionId, new ResumeSessionConfig
{
OnPermissionRequest = (_, _) =>
Task.FromResult(new PermissionRequestResult { Kind = "denied-no-approval-rule-and-could-not-request-from-user" })
});
var permissionDenied = false;

session2.On(evt =>
Expand Down
2 changes: 1 addition & 1 deletion go/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ That's it! When your application calls `copilot.NewClient` without a `CLIPath` n
- `Stop() error` - Stop the CLI server
- `ForceStop()` - Forcefully stop without graceful cleanup
- `CreateSession(config *SessionConfig) (*Session, error)` - Create a new session
- `ResumeSession(sessionID string) (*Session, error)` - Resume an existing session
- `ResumeSession(sessionID string, config *ResumeSessionConfig) (*Session, error)` - Resume an existing session
- `ResumeSessionWithOptions(sessionID string, config *ResumeSessionConfig) (*Session, error)` - Resume with additional configuration
- `ListSessions(filter *SessionListFilter) ([]SessionMetadata, error)` - List sessions (with optional filter)
- `DeleteSession(sessionID string) error` - Delete a session permanently
Expand Down
21 changes: 14 additions & 7 deletions go/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,17 +426,20 @@ func (c *Client) ensureConnected() error {
// If the client is not connected and AutoStart is enabled, this will automatically
// start the connection.
//
// The config parameter is optional; pass nil for default settings.
// The config parameter is required and must include an OnPermissionRequest handler.
//
// Returns the created session or an error if session creation fails.
//
// Example:
//
// // Basic session
// session, err := client.CreateSession(context.Background(), nil)
// session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{
// OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
// })
//
// // Session with model and tools
// session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{
// OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
// Model: "gpt-4",
// Tools: []copilot.Tool{
// {
Expand Down Expand Up @@ -518,15 +521,18 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
return session, nil
}

// ResumeSession resumes an existing conversation session by its ID using default options.
// ResumeSession resumes an existing conversation session by its ID.
//
// This is a convenience method that calls [Client.ResumeSessionWithOptions] with nil config.
// This is a convenience method that calls [Client.ResumeSessionWithOptions].
// The config must include an OnPermissionRequest handler.
//
// Example:
//
// session, err := client.ResumeSession(context.Background(), "session-123")
func (c *Client) ResumeSession(ctx context.Context, sessionID string) (*Session, error) {
return c.ResumeSessionWithOptions(ctx, sessionID, nil)
// session, err := client.ResumeSession(context.Background(), "session-123", &copilot.ResumeSessionConfig{
// OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
// })
func (c *Client) ResumeSession(ctx context.Context, sessionID string, config *ResumeSessionConfig) (*Session, error) {
return c.ResumeSessionWithOptions(ctx, sessionID, config)
}
Comment thread
SteveSandersonMS marked this conversation as resolved.
Outdated

// ResumeSessionWithOptions resumes an existing conversation session with additional configuration.
Expand All @@ -537,6 +543,7 @@ func (c *Client) ResumeSession(ctx context.Context, sessionID string) (*Session,
// Example:
//
// session, err := client.ResumeSessionWithOptions(context.Background(), "session-123", &copilot.ResumeSessionConfig{
// OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
// Tools: []copilot.Tool{myNewTool},
// })
func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, config *ResumeSessionConfig) (*Session, error) {
Expand Down
24 changes: 17 additions & 7 deletions go/internal/e2e/permissions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func TestPermissions(t *testing.T) {
ctx.ConfigureForTest(t)

onPermissionRequest := func(request copilot.PermissionRequest, invocation copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) {
return copilot.PermissionRequestResult{Kind: "denied-interactively-by-user"}, nil
return copilot.PermissionRequestResult{Kind: "denied-no-approval-rule-and-could-not-request-from-user"}, nil
}

session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
Expand Down Expand Up @@ -157,10 +157,14 @@ func TestPermissions(t *testing.T) {
}
})

t.Run("should deny tool operations by default when no handler is provided", func(t *testing.T) {
t.Run("should deny tool operations when handler explicitly denies", func(t *testing.T) {
ctx.ConfigureForTest(t)

session, err := client.CreateSession(t.Context(), nil)
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: func(request copilot.PermissionRequest, invocation copilot.ToolInvocation) (copilot.PermissionRequestResult, error) {
return copilot.PermissionRequestResult{Kind: "denied-no-approval-rule-and-could-not-request-from-user"}, nil
},
})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
}
Expand Down Expand Up @@ -192,7 +196,7 @@ func TestPermissions(t *testing.T) {
}
})

t.Run("should deny tool operations by default when no handler is provided after resume", func(t *testing.T) {
t.Run("should deny tool operations when handler explicitly denies after resume", func(t *testing.T) {
ctx.ConfigureForTest(t)

session1, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
Expand All @@ -206,7 +210,11 @@ func TestPermissions(t *testing.T) {
t.Fatalf("Failed to send message: %v", err)
}

session2, err := client.ResumeSession(t.Context(), sessionID)
session2, err := client.ResumeSession(t.Context(), sessionID, &copilot.ResumeSessionConfig{
OnPermissionRequest: func(request copilot.PermissionRequest, invocation copilot.ToolInvocation) (copilot.PermissionRequestResult, error) {
return copilot.PermissionRequestResult{Kind: "denied-no-approval-rule-and-could-not-request-from-user"}, nil
},
})
if err != nil {
t.Fatalf("Failed to resume session: %v", err)
}
Expand Down Expand Up @@ -238,10 +246,12 @@ func TestPermissions(t *testing.T) {
}
})

t.Run("without permission handler", func(t *testing.T) {
t.Run("should work with approve-all permission handler", func(t *testing.T) {
ctx.ConfigureForTest(t)

session, err := client.CreateSession(t.Context(), nil)
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
}
Expand Down
16 changes: 12 additions & 4 deletions go/internal/e2e/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,9 @@ func TestSession(t *testing.T) {
}

// Resume using the same client
session2, err := client.ResumeSession(t.Context(), sessionID)
session2, err := client.ResumeSession(t.Context(), sessionID, &copilot.ResumeSessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
})
if err != nil {
t.Fatalf("Failed to resume session: %v", err)
}
Expand Down Expand Up @@ -391,7 +393,9 @@ func TestSession(t *testing.T) {
newClient := ctx.NewClient()
defer newClient.ForceStop()

session2, err := newClient.ResumeSession(t.Context(), sessionID)
session2, err := newClient.ResumeSession(t.Context(), sessionID, &copilot.ResumeSessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
})
if err != nil {
t.Fatalf("Failed to resume session: %v", err)
}
Expand Down Expand Up @@ -428,7 +432,9 @@ func TestSession(t *testing.T) {
t.Run("should throw error when resuming non-existent session", func(t *testing.T) {
ctx.ConfigureForTest(t)

_, err := client.ResumeSession(t.Context(), "non-existent-session-id")
_, err := client.ResumeSession(t.Context(), "non-existent-session-id", &copilot.ResumeSessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
})
if err == nil {
t.Error("Expected error when resuming non-existent session")
}
Expand Down Expand Up @@ -881,7 +887,9 @@ func TestSession(t *testing.T) {
}

// Verify we cannot resume the deleted session
_, err = client.ResumeSession(t.Context(), sessionID)
_, err = client.ResumeSession(t.Context(), sessionID, &copilot.ResumeSessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
})
if err == nil {
t.Error("Expected error when resuming deleted session")
}
Expand Down
14 changes: 6 additions & 8 deletions nodejs/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,10 +494,11 @@ export class CopilotClient {
* @example
* ```typescript
* // Basic session
* const session = await client.createSession();
* const session = await client.createSession({ onPermissionRequest: approveAll });
*
* // Session with model and tools
* const session = await client.createSession({
* onPermissionRequest: approveAll,
* model: "gpt-4",
* tools: [{
* name: "get_weather",
Expand Down Expand Up @@ -557,9 +558,7 @@ export class CopilotClient {
};
const session = new CopilotSession(sessionId, this.connection!, workspacePath);
session.registerTools(config.tools);
if (config.onPermissionRequest) {
session.registerPermissionHandler(config.onPermissionRequest);
}
session.registerPermissionHandler(config.onPermissionRequest);
if (config.onUserInputRequest) {
session.registerUserInputHandler(config.onUserInputRequest);
}
Expand All @@ -586,10 +585,11 @@ export class CopilotClient {
* @example
* ```typescript
* // Resume a previous session
* const session = await client.resumeSession("session-123");
* const session = await client.resumeSession("session-123", { onPermissionRequest: approveAll });
*
* // Resume with new tools
* const session = await client.resumeSession("session-123", {
* onPermissionRequest: approveAll,
* tools: [myNewTool]
* });
* ```
Expand Down Expand Up @@ -644,9 +644,7 @@ export class CopilotClient {
};
const session = new CopilotSession(resumedSessionId, this.connection!, workspacePath);
session.registerTools(config.tools);
if (config.onPermissionRequest) {
session.registerPermissionHandler(config.onPermissionRequest);
}
session.registerPermissionHandler(config.onPermissionRequest);
if (config.onUserInputRequest) {
session.registerUserInputHandler(config.onUserInputRequest);
}
Expand Down
21 changes: 14 additions & 7 deletions nodejs/test/e2e/permissions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,14 @@ describe("Permission callbacks", async () => {
await session.destroy();
});

it("should deny tool operations by default when no handler is provided", async () => {
it("should deny tool operations when handler explicitly denies", async () => {
let permissionDenied = false;

const session = await client.createSession({ onPermissionRequest: approveAll });
const session = await client.createSession({
onPermissionRequest: () => ({
kind: "denied-no-approval-rule-and-could-not-request-from-user",
}),
});
session.on((event) => {
if (
event.type === "tool.execution_complete" &&
Expand All @@ -85,12 +89,16 @@ describe("Permission callbacks", async () => {
await session.destroy();
});

it("should deny tool operations by default when no handler is provided after resume", async () => {
it("should deny tool operations when handler explicitly denies after resume", async () => {
const session1 = await client.createSession({ onPermissionRequest: approveAll });
const sessionId = session1.sessionId;
await session1.sendAndWait({ prompt: "What is 1+1?" });

const session2 = await client.resumeSession(sessionId, { onPermissionRequest: approveAll });
const session2 = await client.resumeSession(sessionId, {
onPermissionRequest: () => ({
kind: "denied-no-approval-rule-and-could-not-request-from-user",
}),
});
let permissionDenied = false;
session2.on((event) => {
if (
Expand All @@ -109,8 +117,7 @@ describe("Permission callbacks", async () => {
await session2.destroy();
});

it("should work without permission handler (default behavior)", async () => {
// Create session without onPermissionRequest handler
it("should work with approve-all permission handler", async () => {
const session = await client.createSession({ onPermissionRequest: approveAll });

const message = await session.sendAndWait({
Expand Down Expand Up @@ -147,7 +154,7 @@ describe("Permission callbacks", async () => {
it("should resume session with permission handler", async () => {
const permissionRequests: PermissionRequest[] = [];

// Create session without permission handler
// Create initial session
const session1 = await client.createSession({ onPermissionRequest: approveAll });
const sessionId = session1.sessionId;
await session1.sendAndWait({ prompt: "What is 1+1?" });
Expand Down
12 changes: 6 additions & 6 deletions python/copilot/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,10 +434,11 @@ async def create_session(self, config: SessionConfig) -> CopilotSession:

Example:
>>> # Basic session
>>> session = await client.create_session()
>>> session = await client.create_session({"on_permission_request": PermissionHandler.approve_all})
>>>
>>> # Session with model and streaming
>>> session = await client.create_session({
... "on_permission_request": PermissionHandler.approve_all,
... "model": "gpt-4",
... "streaming": True
... })
Comment thread
SteveSandersonMS marked this conversation as resolved.
Expand Down Expand Up @@ -575,8 +576,7 @@ async def create_session(self, config: SessionConfig) -> CopilotSession:
workspace_path = response.get("workspacePath")
session = CopilotSession(session_id, self._client, workspace_path)
session._register_tools(tools)
if on_permission_request:
session._register_permission_handler(on_permission_request)
session._register_permission_handler(on_permission_request)
if on_user_input_request:
session._register_user_input_handler(on_user_input_request)
if hooks:
Expand Down Expand Up @@ -606,10 +606,11 @@ async def resume_session(self, session_id: str, config: ResumeSessionConfig) ->

Example:
>>> # Resume a previous session
>>> session = await client.resume_session("session-123")
>>> session = await client.resume_session("session-123", {"on_permission_request": PermissionHandler.approve_all})
>>>
>>> # Resume with new tools
>>> session = await client.resume_session("session-123", {
... "on_permission_request": PermissionHandler.approve_all,
... "tools": [my_new_tool]
... })
"""
Expand Down Expand Up @@ -756,8 +757,7 @@ async def resume_session(self, session_id: str, config: ResumeSessionConfig) ->
workspace_path = response.get("workspacePath")
session = CopilotSession(resumed_session_id, self._client, workspace_path)
session._register_tools(cfg.get("tools"))
if on_permission_request:
session._register_permission_handler(on_permission_request)
session._register_permission_handler(on_permission_request)
if on_user_input_request:
session._register_user_input_handler(on_user_input_request)
if hooks:
Expand Down
Loading
Loading