Skip to content

Commit dde88fb

Browse files
jamesmontemagnoCopilotCopilot
authored
Add Permission Handling documentation to all language READMEs (#879)
* Add Permission Handling documentation to all language READMEs Co-authored-by: jamesmontemagno <1676321+jamesmontemagno@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jamesmontemagno <1676321+jamesmontemagno@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 7d8fb51 commit dde88fb

File tree

4 files changed

+339
-11
lines changed

4 files changed

+339
-11
lines changed

dotnet/README.md

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ using GitHub.Copilot.SDK;
2828
await using var client = new CopilotClient();
2929
await client.StartAsync();
3030

31-
// Create a session
31+
// Create a session (OnPermissionRequest is required)
3232
await using var session = await client.CreateSessionAsync(new SessionConfig
3333
{
34-
Model = "gpt-5"
34+
Model = "gpt-5",
35+
OnPermissionRequest = PermissionHandler.ApproveAll,
3536
});
3637

3738
// Wait for response using session.idle event
@@ -110,13 +111,18 @@ Create a new conversation session.
110111
- `Provider` - Custom API provider configuration (BYOK)
111112
- `Streaming` - Enable streaming of response chunks (default: false)
112113
- `InfiniteSessions` - Configure automatic context compaction (see below)
114+
- `OnPermissionRequest` - **Required.** Handler called before each tool execution to approve or deny it. Use `PermissionHandler.ApproveAll` to allow everything, or provide a custom function for fine-grained control. See [Permission Handling](#permission-handling) section.
113115
- `OnUserInputRequest` - Handler for user input requests from the agent (enables ask_user tool). See [User Input Requests](#user-input-requests) section.
114116
- `Hooks` - Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section.
115117

116118
##### `ResumeSessionAsync(string sessionId, ResumeSessionConfig? config = null): Task<CopilotSession>`
117119

118120
Resume an existing session. Returns the session with `WorkspacePath` populated if infinite sessions were enabled.
119121

122+
**ResumeSessionConfig:**
123+
124+
- `OnPermissionRequest` - **Required.** Handler called before each tool execution to approve or deny it. See [Permission Handling](#permission-handling) section.
125+
120126
##### `PingAsync(string? message = null): Task<PingResponse>`
121127

122128
Ping the server to check connectivity.
@@ -573,6 +579,84 @@ Trace context (`traceparent`/`tracestate`) is automatically propagated between t
573579

574580
No extra dependencies — uses built-in `System.Diagnostics.Activity`.
575581

582+
## Permission Handling
583+
584+
An `OnPermissionRequest` handler is **required** whenever you create or resume a session. The handler is called before the agent executes each tool (file writes, shell commands, custom tools, etc.) and must return a decision.
585+
586+
### Approve All (simplest)
587+
588+
Use the built-in `PermissionHandler.ApproveAll` helper to allow every tool call without any checks:
589+
590+
```csharp
591+
using GitHub.Copilot.SDK;
592+
593+
var session = await client.CreateSessionAsync(new SessionConfig
594+
{
595+
Model = "gpt-5",
596+
OnPermissionRequest = PermissionHandler.ApproveAll,
597+
});
598+
```
599+
600+
### Custom Permission Handler
601+
602+
Provide your own `PermissionRequestHandler` delegate to inspect each request and apply custom logic:
603+
604+
```csharp
605+
var session = await client.CreateSessionAsync(new SessionConfig
606+
{
607+
Model = "gpt-5",
608+
OnPermissionRequest = async (request, invocation) =>
609+
{
610+
// request.Kind — string discriminator for the type of operation being requested:
611+
// "shell" — executing a shell command
612+
// "write" — writing or editing a file
613+
// "read" — reading a file
614+
// "mcp" — calling an MCP tool
615+
// "custom_tool" — calling one of your registered tools
616+
// "url" — fetching a URL
617+
// "memory" — accessing or modifying assistant memory
618+
// "hook" — invoking a registered hook
619+
// request.ToolCallId — the tool call that triggered this request
620+
// request.ToolName — name of the tool (for custom-tool / mcp)
621+
// request.FileName — file being written (for write)
622+
// request.FullCommandText — full shell command text (for shell)
623+
624+
if (request.Kind == "shell")
625+
{
626+
// Deny shell commands
627+
return new PermissionRequestResult { Kind = PermissionRequestResultKind.DeniedInteractivelyByUser };
628+
}
629+
630+
return new PermissionRequestResult { Kind = PermissionRequestResultKind.Approved };
631+
}
632+
});
633+
```
634+
635+
### Permission Result Kinds
636+
637+
| Value | Meaning |
638+
|-------|---------|
639+
| `PermissionRequestResultKind.Approved` | Allow the tool to run |
640+
| `PermissionRequestResultKind.DeniedInteractivelyByUser` | User explicitly denied the request |
641+
| `PermissionRequestResultKind.DeniedCouldNotRequestFromUser` | No approval rule matched and user could not be asked |
642+
| `PermissionRequestResultKind.DeniedByRules` | Denied by a policy rule |
643+
| `PermissionRequestResultKind.NoResult` | Leave the permission request unanswered (the SDK returns without calling the RPC). Not allowed for protocol v2 permission requests (will be rejected). |
644+
645+
### Resuming Sessions
646+
647+
Pass `OnPermissionRequest` when resuming a session too — it is required:
648+
649+
```csharp
650+
var session = await client.ResumeSessionAsync("session-id", new ResumeSessionConfig
651+
{
652+
OnPermissionRequest = PermissionHandler.ApproveAll,
653+
});
654+
```
655+
656+
### Per-Tool Skip Permission
657+
658+
To let a specific custom tool bypass the permission prompt entirely, set `skip_permission = true` in the tool's `AdditionalProperties`. See [Skipping Permission Prompts](#skipping-permission-prompts) under Tools.
659+
576660
## User Input Requests
577661

578662
Enable the agent to ask questions to the user using the `ask_user` tool by providing an `OnUserInputRequest` handler:

go/README.md

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@ func main() {
4444
}
4545
defer client.Stop()
4646

47-
// Create a session
47+
// Create a session (OnPermissionRequest is required)
4848
session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{
49-
Model: "gpt-5",
49+
Model: "gpt-5",
50+
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
5051
})
5152
if err != nil {
5253
log.Fatal(err)
@@ -153,11 +154,13 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec
153154
- `Provider` (\*ProviderConfig): Custom API provider configuration (BYOK). See [Custom Providers](#custom-providers) section.
154155
- `Streaming` (bool): Enable streaming delta events
155156
- `InfiniteSessions` (\*InfiniteSessionConfig): Automatic context compaction configuration
157+
- `OnPermissionRequest` (PermissionHandlerFunc): **Required.** Handler called before each tool execution to approve or deny it. Use `copilot.PermissionHandler.ApproveAll` to allow everything, or provide a custom function for fine-grained control. See [Permission Handling](#permission-handling) section.
156158
- `OnUserInputRequest` (UserInputHandler): Handler for user input requests from the agent (enables ask_user tool). See [User Input Requests](#user-input-requests) section.
157159
- `Hooks` (\*SessionHooks): Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section.
158160

159161
**ResumeSessionConfig:**
160162

163+
- `OnPermissionRequest` (PermissionHandlerFunc): **Required.** Handler called before each tool execution to approve or deny it. See [Permission Handling](#permission-handling) section.
161164
- `Tools` ([]Tool): Tools to expose when resuming
162165
- `ReasoningEffort` (string): Reasoning effort level for models that support it
163166
- `Provider` (\*ProviderConfig): Custom API provider configuration (BYOK). See [Custom Providers](#custom-providers) section.
@@ -499,6 +502,77 @@ Trace context (`traceparent`/`tracestate`) is automatically propagated between t
499502
500503
Dependency: `go.opentelemetry.io/otel`
501504

505+
## Permission Handling
506+
507+
An `OnPermissionRequest` handler is **required** whenever you create or resume a session. The handler is called before the agent executes each tool (file writes, shell commands, custom tools, etc.) and must return a decision.
508+
509+
### Approve All (simplest)
510+
511+
Use the built-in `PermissionHandler.ApproveAll` helper to allow every tool call without any checks:
512+
513+
```go
514+
session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{
515+
Model: "gpt-5",
516+
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
517+
})
518+
```
519+
520+
### Custom Permission Handler
521+
522+
Provide your own `PermissionHandlerFunc` to inspect each request and apply custom logic:
523+
524+
```go
525+
session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{
526+
Model: "gpt-5",
527+
OnPermissionRequest: func(request copilot.PermissionRequest, invocation copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) {
528+
// request.Kind — what type of operation is being requested:
529+
// copilot.KindShell — executing a shell command
530+
// copilot.Write — writing or editing a file
531+
// copilot.Read — reading a file
532+
// copilot.MCP — calling an MCP tool
533+
// copilot.CustomTool — calling one of your registered tools
534+
// copilot.URL — fetching a URL
535+
// copilot.Memory — accessing or updating Copilot-managed memory
536+
// copilot.Hook — invoking a registered hook
537+
// request.ToolCallID — pointer to the tool call that triggered this request
538+
// request.ToolName — pointer to the name of the tool (for custom-tool / mcp)
539+
// request.FileName — pointer to the file being written (for write)
540+
// request.FullCommandText — pointer to the full shell command (for shell)
541+
542+
if request.Kind == copilot.KindShell {
543+
// Deny shell commands
544+
return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindDeniedInteractivelyByUser}, nil
545+
}
546+
547+
return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindApproved}, nil
548+
},
549+
})
550+
```
551+
552+
### Permission Result Kinds
553+
554+
| Constant | Meaning |
555+
|----------|---------|
556+
| `PermissionRequestResultKindApproved` | Allow the tool to run |
557+
| `PermissionRequestResultKindDeniedInteractivelyByUser` | User explicitly denied the request |
558+
| `PermissionRequestResultKindDeniedCouldNotRequestFromUser` | No approval rule matched and user could not be asked |
559+
| `PermissionRequestResultKindDeniedByRules` | Denied by a policy rule |
560+
| `PermissionRequestResultKindNoResult` | Leave the permission request unanswered (protocol v1 only; not allowed for protocol v2) |
561+
562+
### Resuming Sessions
563+
564+
Pass `OnPermissionRequest` when resuming a session too — it is required:
565+
566+
```go
567+
session, err := client.ResumeSession(context.Background(), sessionID, &copilot.ResumeSessionConfig{
568+
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
569+
})
570+
```
571+
572+
### Per-Tool Skip Permission
573+
574+
To let a specific custom tool bypass the permission prompt entirely, set `SkipPermission = true` on the tool. See [Skipping Permission Prompts](#skipping-permission-prompts) under Tools.
575+
502576
## User Input Requests
503577

504578
Enable the agent to ask questions to the user using the `ask_user` tool by providing an `OnUserInputRequest` handler:

nodejs/README.md

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,16 @@ npm start
2626
## Quick Start
2727

2828
```typescript
29-
import { CopilotClient } from "@github/copilot-sdk";
29+
import { CopilotClient, approveAll } from "@github/copilot-sdk";
3030

3131
// Create and start client
3232
const client = new CopilotClient();
3333
await client.start();
3434

35-
// Create a session
35+
// Create a session (onPermissionRequest is required)
3636
const session = await client.createSession({
3737
model: "gpt-5",
38+
onPermissionRequest: approveAll,
3839
});
3940

4041
// Wait for response using typed event handlers
@@ -59,7 +60,7 @@ await client.stop();
5960
Sessions also support `Symbol.asyncDispose` for use with [`await using`](https://github.com/tc39/proposal-explicit-resource-management) (TypeScript 5.2+/Node.js 18.0+):
6061

6162
```typescript
62-
await using session = await client.createSession({ model: "gpt-5" });
63+
await using session = await client.createSession({ model: "gpt-5", onPermissionRequest: approveAll });
6364
// session is automatically disconnected when leaving scope
6465
```
6566

@@ -114,6 +115,7 @@ Create a new conversation session.
114115
- `systemMessage?: SystemMessageConfig` - System message customization (see below)
115116
- `infiniteSessions?: InfiniteSessionConfig` - Configure automatic context compaction (see below)
116117
- `provider?: ProviderConfig` - Custom API provider configuration (BYOK - Bring Your Own Key). See [Custom Providers](#custom-providers) section.
118+
- `onPermissionRequest: PermissionHandler` - **Required.** Handler called before each tool execution to approve or deny it. Use `approveAll` to allow everything, or provide a custom function for fine-grained control. See [Permission Handling](#permission-handling) section.
117119
- `onUserInputRequest?: UserInputHandler` - Handler for user input requests from the agent. Enables the `ask_user` tool. See [User Input Requests](#user-input-requests) section.
118120
- `hooks?: SessionHooks` - Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section.
119121

@@ -648,6 +650,82 @@ const client = new CopilotClient({
648650

649651
Inbound trace context from the CLI is available on the `ToolInvocation` object passed to tool handlers as `traceparent` and `tracestate` fields. See the [OpenTelemetry guide](../docs/observability/opentelemetry.md) for a full wire-up example.
650652

653+
## Permission Handling
654+
655+
An `onPermissionRequest` handler is **required** whenever you create or resume a session. The handler is called before the agent executes each tool (file writes, shell commands, custom tools, etc.) and must return a decision.
656+
657+
### Approve All (simplest)
658+
659+
Use the built-in `approveAll` helper to allow every tool call without any checks:
660+
661+
```typescript
662+
import { CopilotClient, approveAll } from "@github/copilot-sdk";
663+
664+
const session = await client.createSession({
665+
model: "gpt-5",
666+
onPermissionRequest: approveAll,
667+
});
668+
```
669+
670+
### Custom Permission Handler
671+
672+
Provide your own function to inspect each request and apply custom logic:
673+
674+
```typescript
675+
import type { PermissionRequest, PermissionRequestResult } from "@github/copilot-sdk";
676+
677+
const session = await client.createSession({
678+
model: "gpt-5",
679+
onPermissionRequest: (request: PermissionRequest, invocation): PermissionRequestResult => {
680+
// request.kind — what type of operation is being requested:
681+
// "shell" — executing a shell command
682+
// "write" — writing or editing a file
683+
// "read" — reading a file
684+
// "mcp" — calling an MCP tool
685+
// "custom-tool" — calling one of your registered tools
686+
// "url" — fetching a URL
687+
// "memory" — storing or retrieving persistent session memory
688+
// "hook" — invoking a server-side hook or integration
689+
// (additional kinds may be added; include a default case in handlers)
690+
// request.toolCallId — the tool call that triggered this request
691+
// request.toolName — name of the tool (for custom-tool / mcp)
692+
// request.fileName — file being written (for write)
693+
// request.fullCommandText — full shell command (for shell)
694+
695+
if (request.kind === "shell") {
696+
// Deny shell commands
697+
return { kind: "denied-interactively-by-user" };
698+
}
699+
700+
return { kind: "approved" };
701+
},
702+
});
703+
```
704+
705+
### Permission Result Kinds
706+
707+
| Kind | Meaning |
708+
|------|---------|
709+
| `"approved"` | Allow the tool to run |
710+
| `"denied-interactively-by-user"` | User explicitly denied the request |
711+
| `"denied-no-approval-rule-and-could-not-request-from-user"` | No approval rule matched and user could not be asked |
712+
| `"denied-by-rules"` | Denied by a policy rule |
713+
| `"denied-by-content-exclusion-policy"` | Denied due to a content exclusion policy |
714+
| `"no-result"` | Leave the request unanswered (only valid with protocol v1; rejected by protocol v2 servers) |
715+
### Resuming Sessions
716+
717+
Pass `onPermissionRequest` when resuming a session too — it is required:
718+
719+
```typescript
720+
const session = await client.resumeSession("session-id", {
721+
onPermissionRequest: approveAll,
722+
});
723+
```
724+
725+
### Per-Tool Skip Permission
726+
727+
To let a specific custom tool bypass the permission prompt entirely, set `skipPermission: true` on the tool definition. See [Skipping Permission Prompts](#skipping-permission-prompts) under Tools.
728+
651729
## User Input Requests
652730

653731
Enable the agent to ask questions to the user using the `ask_user` tool by providing an `onUserInputRequest` handler:

0 commit comments

Comments
 (0)