Skip to content

Commit 08adc4e

Browse files
committed
Merge remote-tracking branch 'origin/main' into add-test-scenarios
2 parents e88df1b + 886e5aa commit 08adc4e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+708
-78
lines changed

docs/compatibility.md

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -124,20 +124,13 @@ The `--share` option is not available via SDK. Workarounds:
124124

125125
### Permission Control
126126

127+
The SDK uses a **deny-by-default** permission model. All permission requests (file writes, shell commands, URL fetches, etc.) are denied unless your app provides an `onPermissionRequest` handler.
128+
127129
Instead of `--allow-all-paths` or `--yolo`, use the permission handler:
128130

129131
```typescript
130132
const session = await client.createSession({
131-
onPermissionRequest: async (request) => {
132-
// Auto-approve everything (equivalent to --yolo)
133-
return { approved: true };
134-
135-
// Or implement custom logic
136-
if (request.kind === "shell") {
137-
return { approved: request.command.startsWith("git") };
138-
}
139-
return { approved: true };
140-
},
133+
onPermissionRequest: approveAll,
141134
});
142135
```
143136

dotnet/samples/Chat.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
using GitHub.Copilot.SDK;
22

33
await using var client = new CopilotClient();
4-
await using var session = await client.CreateSessionAsync();
4+
await using var session = await client.CreateSessionAsync(new SessionConfig
5+
{
6+
OnPermissionRequest = PermissionHandler.ApproveAll
7+
});
58

69
using var _ = session.On(evt =>
710
{

dotnet/src/Client.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,13 +377,14 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig? config = nul
377377
var request = new CreateSessionRequest(
378378
config?.Model,
379379
config?.SessionId,
380+
config?.ClientName,
380381
config?.ReasoningEffort,
381382
config?.Tools?.Select(ToolDefinition.FromAIFunction).ToList(),
382383
config?.SystemMessage,
383384
config?.AvailableTools,
384385
config?.ExcludedTools,
385386
config?.Provider,
386-
config?.OnPermissionRequest != null ? true : null,
387+
(bool?)true,
387388
config?.OnUserInputRequest != null ? true : null,
388389
hasHooks ? true : null,
389390
config?.WorkingDirectory,
@@ -460,14 +461,15 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
460461

461462
var request = new ResumeSessionRequest(
462463
sessionId,
464+
config?.ClientName,
463465
config?.Model,
464466
config?.ReasoningEffort,
465467
config?.Tools?.Select(ToolDefinition.FromAIFunction).ToList(),
466468
config?.SystemMessage,
467469
config?.AvailableTools,
468470
config?.ExcludedTools,
469471
config?.Provider,
470-
config?.OnPermissionRequest != null ? true : null,
472+
(bool?)true,
471473
config?.OnUserInputRequest != null ? true : null,
472474
hasHooks ? true : null,
473475
config?.WorkingDirectory,
@@ -1381,6 +1383,7 @@ public static string Escape(string arg)
13811383
internal record CreateSessionRequest(
13821384
string? Model,
13831385
string? SessionId,
1386+
string? ClientName,
13841387
string? ReasoningEffort,
13851388
List<ToolDefinition>? Tools,
13861389
SystemMessageConfig? SystemMessage,
@@ -1415,6 +1418,7 @@ internal record CreateSessionResponse(
14151418

14161419
internal record ResumeSessionRequest(
14171420
string SessionId,
1421+
string? ClientName,
14181422
string? Model,
14191423
string? ReasoningEffort,
14201424
List<ToolDefinition>? Tools,

dotnet/src/PermissionHandlers.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
namespace GitHub.Copilot.SDK;
6+
7+
/// <summary>Provides pre-built <see cref="PermissionRequestHandler"/> implementations.</summary>
8+
public static class PermissionHandler
9+
{
10+
/// <summary>A <see cref="PermissionRequestHandler"/> that approves all permission requests.</summary>
11+
public static PermissionRequestHandler ApproveAll { get; } =
12+
(_, _) => Task.FromResult(new PermissionRequestResult { Kind = "approved" });
13+
}

dotnet/src/Session.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public partial class CopilotSession : IAsyncDisposable
4747
private readonly HashSet<SessionEventHandler> _eventHandlers = new();
4848
private readonly Dictionary<string, AIFunction> _toolHandlers = new();
4949
private readonly JsonRpc _rpc;
50-
private PermissionHandler? _permissionHandler;
50+
private PermissionRequestHandler? _permissionHandler;
5151
private readonly SemaphoreSlim _permissionHandlerLock = new(1, 1);
5252
private UserInputHandler? _userInputHandler;
5353
private readonly SemaphoreSlim _userInputHandlerLock = new(1, 1);
@@ -292,7 +292,7 @@ internal void RegisterTools(ICollection<AIFunction> tools)
292292
/// When the assistant needs permission to perform certain actions (e.g., file operations),
293293
/// this handler is called to approve or deny the request.
294294
/// </remarks>
295-
internal void RegisterPermissionHandler(PermissionHandler handler)
295+
internal void RegisterPermissionHandler(PermissionRequestHandler handler)
296296
{
297297
_permissionHandlerLock.Wait();
298298
try
@@ -313,7 +313,7 @@ internal void RegisterPermissionHandler(PermissionHandler handler)
313313
internal async Task<PermissionRequestResult> HandlePermissionRequestAsync(JsonElement permissionRequestData)
314314
{
315315
await _permissionHandlerLock.WaitAsync();
316-
PermissionHandler? handler;
316+
PermissionRequestHandler? handler;
317317
try
318318
{
319319
handler = _permissionHandler;

dotnet/src/Types.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ public class PermissionInvocation
166166
public string SessionId { get; set; } = string.Empty;
167167
}
168168

169-
public delegate Task<PermissionRequestResult> PermissionHandler(PermissionRequest request, PermissionInvocation invocation);
169+
public delegate Task<PermissionRequestResult> PermissionRequestHandler(PermissionRequest request, PermissionInvocation invocation);
170170

171171
// ============================================================================
172172
// User Input Handler Types
@@ -745,6 +745,7 @@ protected SessionConfig(SessionConfig? other)
745745
if (other is null) return;
746746

747747
AvailableTools = other.AvailableTools is not null ? [.. other.AvailableTools] : null;
748+
ClientName = other.ClientName;
748749
ConfigDir = other.ConfigDir;
749750
CustomAgents = other.CustomAgents is not null ? [.. other.CustomAgents] : null;
750751
DisabledSkills = other.DisabledSkills is not null ? [.. other.DisabledSkills] : null;
@@ -768,6 +769,13 @@ protected SessionConfig(SessionConfig? other)
768769
}
769770

770771
public string? SessionId { get; set; }
772+
773+
/// <summary>
774+
/// Client name to identify the application using the SDK.
775+
/// Included in the User-Agent header for API requests.
776+
/// </summary>
777+
public string? ClientName { get; set; }
778+
771779
public string? Model { get; set; }
772780

773781
/// <summary>
@@ -793,7 +801,7 @@ protected SessionConfig(SessionConfig? other)
793801
/// Handler for permission requests from the server.
794802
/// When provided, the server will call this handler to request permission for operations.
795803
/// </summary>
796-
public PermissionHandler? OnPermissionRequest { get; set; }
804+
public PermissionRequestHandler? OnPermissionRequest { get; set; }
797805

798806
/// <summary>
799807
/// Handler for user input requests from the agent.
@@ -874,6 +882,7 @@ protected ResumeSessionConfig(ResumeSessionConfig? other)
874882
if (other is null) return;
875883

876884
AvailableTools = other.AvailableTools is not null ? [.. other.AvailableTools] : null;
885+
ClientName = other.ClientName;
877886
ConfigDir = other.ConfigDir;
878887
CustomAgents = other.CustomAgents is not null ? [.. other.CustomAgents] : null;
879888
DisabledSkills = other.DisabledSkills is not null ? [.. other.DisabledSkills] : null;
@@ -896,6 +905,12 @@ protected ResumeSessionConfig(ResumeSessionConfig? other)
896905
WorkingDirectory = other.WorkingDirectory;
897906
}
898907

908+
/// <summary>
909+
/// Client name to identify the application using the SDK.
910+
/// Included in the User-Agent header for API requests.
911+
/// </summary>
912+
public string? ClientName { get; set; }
913+
899914
/// <summary>
900915
/// Model to use for this session. Can change the model when resuming.
901916
/// </summary>
@@ -932,7 +947,7 @@ protected ResumeSessionConfig(ResumeSessionConfig? other)
932947
/// Handler for permission requests from the server.
933948
/// When provided, the server will call this handler to request permission for operations.
934949
/// </summary>
935-
public PermissionHandler? OnPermissionRequest { get; set; }
950+
public PermissionRequestHandler? OnPermissionRequest { get; set; }
936951

937952
/// <summary>
938953
/// Handler for user input requests from the agent.

dotnet/test/CloneTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
7878
var original = new SessionConfig
7979
{
8080
SessionId = "test-session",
81+
ClientName = "my-app",
8182
Model = "gpt-4",
8283
ReasoningEffort = "high",
8384
ConfigDir = "/config",
@@ -94,6 +95,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
9495
var clone = original.Clone();
9596

9697
Assert.Equal(original.SessionId, clone.SessionId);
98+
Assert.Equal(original.ClientName, clone.ClientName);
9799
Assert.Equal(original.Model, clone.Model);
98100
Assert.Equal(original.ReasoningEffort, clone.ReasoningEffort);
99101
Assert.Equal(original.ConfigDir, clone.ConfigDir);

dotnet/test/HooksTests.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public async Task Should_Invoke_PreToolUse_Hook_When_Model_Runs_A_Tool()
1717
CopilotSession? session = null;
1818
session = await Client.CreateSessionAsync(new SessionConfig
1919
{
20+
OnPermissionRequest = PermissionHandler.ApproveAll,
2021
Hooks = new SessionHooks
2122
{
2223
OnPreToolUse = (input, invocation) =>
@@ -52,6 +53,7 @@ public async Task Should_Invoke_PostToolUse_Hook_After_Model_Runs_A_Tool()
5253
CopilotSession? session = null;
5354
session = await Client.CreateSessionAsync(new SessionConfig
5455
{
56+
OnPermissionRequest = PermissionHandler.ApproveAll,
5557
Hooks = new SessionHooks
5658
{
5759
OnPostToolUse = (input, invocation) =>
@@ -89,6 +91,7 @@ public async Task Should_Invoke_Both_PreToolUse_And_PostToolUse_Hooks_For_Single
8991

9092
var session = await Client.CreateSessionAsync(new SessionConfig
9193
{
94+
OnPermissionRequest = PermissionHandler.ApproveAll,
9295
Hooks = new SessionHooks
9396
{
9497
OnPreToolUse = (input, invocation) =>
@@ -130,6 +133,7 @@ public async Task Should_Deny_Tool_Execution_When_PreToolUse_Returns_Deny()
130133

131134
var session = await Client.CreateSessionAsync(new SessionConfig
132135
{
136+
OnPermissionRequest = PermissionHandler.ApproveAll,
133137
Hooks = new SessionHooks
134138
{
135139
OnPreToolUse = (input, invocation) =>

dotnet/test/McpAndAgentsTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,8 @@ public async Task Should_Pass_Literal_Env_Values_To_Mcp_Server_Subprocess()
279279

280280
var session = await Client.CreateSessionAsync(new SessionConfig
281281
{
282-
McpServers = mcpServers
282+
McpServers = mcpServers,
283+
OnPermissionRequest = PermissionHandler.ApproveAll,
283284
});
284285

285286
Assert.Matches(@"^[a-f0-9-]+$", session.SessionId);

dotnet/test/PermissionTests.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,30 @@ await session.SendAsync(new MessageOptions
7070
Assert.Equal("protected content", content);
7171
}
7272

73+
[Fact]
74+
public async Task Should_Deny_Tool_Operations_By_Default_When_No_Handler_Is_Provided()
75+
{
76+
var session = await Client.CreateSessionAsync(new SessionConfig());
77+
var permissionDenied = false;
78+
79+
session.On(evt =>
80+
{
81+
if (evt is ToolExecutionCompleteEvent toolEvt &&
82+
!toolEvt.Data.Success &&
83+
toolEvt.Data.Error?.Message.Contains("Permission denied") == true)
84+
{
85+
permissionDenied = true;
86+
}
87+
});
88+
89+
await session.SendAndWaitAsync(new MessageOptions
90+
{
91+
Prompt = "Run 'node --version'"
92+
});
93+
94+
Assert.True(permissionDenied, "Expected a tool.execution_complete event with Permission denied result");
95+
}
96+
7397
[Fact]
7498
public async Task Should_Work_Without_Permission_Handler__Default_Behavior_()
7599
{
@@ -161,6 +185,37 @@ await session.SendAsync(new MessageOptions
161185
Assert.Matches("fail|cannot|unable|permission", message?.Data.Content?.ToLowerInvariant() ?? string.Empty);
162186
}
163187

188+
[Fact]
189+
public async Task Should_Deny_Tool_Operations_By_Default_When_No_Handler_Is_Provided_After_Resume()
190+
{
191+
var session1 = await Client.CreateSessionAsync(new SessionConfig
192+
{
193+
OnPermissionRequest = PermissionHandler.ApproveAll
194+
});
195+
var sessionId = session1.SessionId;
196+
await session1.SendAndWaitAsync(new MessageOptions { Prompt = "What is 1+1?" });
197+
198+
var session2 = await Client.ResumeSessionAsync(sessionId);
199+
var permissionDenied = false;
200+
201+
session2.On(evt =>
202+
{
203+
if (evt is ToolExecutionCompleteEvent toolEvt &&
204+
!toolEvt.Data.Success &&
205+
toolEvt.Data.Error?.Message.Contains("Permission denied") == true)
206+
{
207+
permissionDenied = true;
208+
}
209+
});
210+
211+
await session2.SendAndWaitAsync(new MessageOptions
212+
{
213+
Prompt = "Run 'node --version'"
214+
});
215+
216+
Assert.True(permissionDenied, "Expected a tool.execution_complete event with Permission denied result");
217+
}
218+
164219
[Fact]
165220
public async Task Should_Receive_ToolCallId_In_Permission_Requests()
166221
{

0 commit comments

Comments
 (0)