From 93e82675eb2b6d07dec03de6c6981f6aeb03252d Mon Sep 17 00:00:00 2001
From: Steve Sanderson <SteveSandersonMS@users.noreply.github.com>
Date: Tue, 24 Feb 2026 11:53:06 +0000
Subject: [PATCH 1/5] Add permission checks for SDK-registered custom tools

Add 'custom-tool' to the PermissionRequest kind union in Node.js and
Python types. Update all existing custom tool e2e tests across all four
languages (Node.js, Python, Go, .NET) to provide an onPermissionRequest
handler, and add new e2e tests verifying permission approval and denial
flows for custom tools.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 dotnet/test/ToolsTests.cs                     |  70 +++++++++++-
 go/internal/e2e/tools_test.go                 | 100 ++++++++++++++++++
 nodejs/src/types.ts                           |   2 +-
 nodejs/test/e2e/tools.test.ts                 |  63 +++++++++++
 python/copilot/types.py                       |   2 +-
 python/e2e/test_tools.py                      |  54 ++++++++++
 ...es_custom_tool_when_permission_denied.yaml |  15 +++
 ...s_custom_tool_with_permission_handler.yaml |  20 ++++
 8 files changed, 323 insertions(+), 3 deletions(-)
 create mode 100644 test/snapshots/tools/denies_custom_tool_when_permission_denied.yaml
 create mode 100644 test/snapshots/tools/invokes_custom_tool_with_permission_handler.yaml

diff --git a/dotnet/test/ToolsTests.cs b/dotnet/test/ToolsTests.cs
index 942a09a0..14f42616 100644
--- a/dotnet/test/ToolsTests.cs
+++ b/dotnet/test/ToolsTests.cs
@@ -42,6 +42,7 @@ public async Task Invokes_Custom_Tool()
         var session = await CreateSessionAsync(new SessionConfig
         {
             Tools = [AIFunctionFactory.Create(EncryptString, "encrypt_string")],
+            OnPermissionRequest = PermissionHandler.ApproveAll,
         });
 
         await session.SendAsync(new MessageOptions
@@ -66,7 +67,8 @@ public async Task Handles_Tool_Calling_Errors()
 
         var session = await CreateSessionAsync(new SessionConfig
         {
-            Tools = [getUserLocation]
+            Tools = [getUserLocation],
+            OnPermissionRequest = PermissionHandler.ApproveAll,
         });
 
         await session.SendAsync(new MessageOptions { Prompt = "What is my location? If you can't find out, just say 'unknown'." });
@@ -108,6 +110,7 @@ public async Task Can_Receive_And_Return_Complex_Types()
         var session = await CreateSessionAsync(new SessionConfig
         {
             Tools = [AIFunctionFactory.Create(PerformDbQuery, "db_query", serializerOptions: ToolsTestsJsonContext.Default.Options)],
+            OnPermissionRequest = PermissionHandler.ApproveAll,
         });
 
         await session.SendAsync(new MessageOptions
@@ -154,6 +157,7 @@ public async Task Can_Return_Binary_Result()
         var session = await CreateSessionAsync(new SessionConfig
         {
             Tools = [AIFunctionFactory.Create(GetImage, "get_image")],
+            OnPermissionRequest = PermissionHandler.ApproveAll,
         });
 
         await session.SendAsync(new MessageOptions
@@ -177,4 +181,68 @@ await session.SendAsync(new MessageOptions
             SessionLog = "Returned an image",
         });
     }
+
+    [Fact]
+    public async Task Invokes_Custom_Tool_With_Permission_Handler()
+    {
+        var permissionRequests = new List<PermissionRequest>();
+
+        var session = await Client.CreateSessionAsync(new SessionConfig
+        {
+            Tools = [AIFunctionFactory.Create(EncryptStringForPermission, "encrypt_string")],
+            OnPermissionRequest = (request, invocation) =>
+            {
+                permissionRequests.Add(request);
+                return Task.FromResult(new PermissionRequestResult { Kind = "approved" });
+            },
+        });
+
+        await session.SendAsync(new MessageOptions
+        {
+            Prompt = "Use encrypt_string to encrypt this string: Hello"
+        });
+
+        var assistantMessage = await TestHelper.GetFinalAssistantMessageAsync(session);
+        Assert.NotNull(assistantMessage);
+        Assert.Contains("HELLO", assistantMessage!.Data.Content ?? string.Empty);
+
+        // Should have received a custom-tool permission request
+        Assert.Contains(permissionRequests, r => r.Kind == "custom-tool");
+
+        [Description("Encrypts a string")]
+        static string EncryptStringForPermission([Description("String to encrypt")] string input)
+            => input.ToUpperInvariant();
+    }
+
+    [Fact]
+    public async Task Denies_Custom_Tool_When_Permission_Denied()
+    {
+        var toolHandlerCalled = false;
+
+        var session = await Client.CreateSessionAsync(new SessionConfig
+        {
+            Tools = [AIFunctionFactory.Create(EncryptStringDenied, "encrypt_string")],
+            OnPermissionRequest = (request, invocation) =>
+            {
+                return Task.FromResult(new PermissionRequestResult { Kind = "denied-interactively-by-user" });
+            },
+        });
+
+        await session.SendAsync(new MessageOptions
+        {
+            Prompt = "Use encrypt_string to encrypt this string: Hello"
+        });
+
+        await TestHelper.GetFinalAssistantMessageAsync(session);
+
+        // The tool handler should NOT have been called since permission was denied
+        Assert.False(toolHandlerCalled);
+
+        [Description("Encrypts a string")]
+        string EncryptStringDenied([Description("String to encrypt")] string input)
+        {
+            toolHandlerCalled = true;
+            return input.ToUpperInvariant();
+        }
+    }
 }
diff --git a/go/internal/e2e/tools_test.go b/go/internal/e2e/tools_test.go
index b38e41a6..d6158248 100644
--- a/go/internal/e2e/tools_test.go
+++ b/go/internal/e2e/tools_test.go
@@ -5,6 +5,7 @@ import (
 	"os"
 	"path/filepath"
 	"strings"
+	"sync"
 	"testing"
 
 	copilot "github.com/github/copilot-sdk/go"
@@ -62,6 +63,7 @@ func TestTools(t *testing.T) {
 						return strings.ToUpper(params.Input), nil
 					}),
 			},
+			OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
 		})
 		if err != nil {
 			t.Fatalf("Failed to create session: %v", err)
@@ -95,6 +97,7 @@ func TestTools(t *testing.T) {
 						return nil, errors.New("Melbourne")
 					}),
 			},
+			OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
 		})
 		if err != nil {
 			t.Fatalf("Failed to create session: %v", err)
@@ -213,6 +216,7 @@ func TestTools(t *testing.T) {
 						}, nil
 					}),
 			},
+			OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
 		})
 		if err != nil {
 			t.Fatalf("Failed to create session: %v", err)
@@ -262,4 +266,100 @@ func TestTools(t *testing.T) {
 			t.Errorf("Expected session ID '%s', got '%s'", session.SessionID, receivedInvocation.SessionID)
 		}
 	})
+
+	t.Run("invokes custom tool with permission handler", func(t *testing.T) {
+		ctx.ConfigureForTest(t)
+
+		type EncryptParams struct {
+			Input string `json:"input" jsonschema:"String to encrypt"`
+		}
+
+		var permissionRequests []copilot.PermissionRequest
+		var mu sync.Mutex
+
+		session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
+			Tools: []copilot.Tool{
+				copilot.DefineTool("encrypt_string", "Encrypts a string",
+					func(params EncryptParams, inv copilot.ToolInvocation) (string, error) {
+						return strings.ToUpper(params.Input), nil
+					}),
+			},
+			OnPermissionRequest: func(request copilot.PermissionRequest, invocation copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) {
+				mu.Lock()
+				permissionRequests = append(permissionRequests, request)
+				mu.Unlock()
+				return copilot.PermissionRequestResult{Kind: "approved"}, nil
+			},
+		})
+		if err != nil {
+			t.Fatalf("Failed to create session: %v", err)
+		}
+
+		_, err = session.Send(t.Context(), copilot.MessageOptions{Prompt: "Use encrypt_string to encrypt this string: Hello"})
+		if err != nil {
+			t.Fatalf("Failed to send message: %v", err)
+		}
+
+		answer, err := testharness.GetFinalAssistantMessage(t.Context(), session)
+		if err != nil {
+			t.Fatalf("Failed to get assistant message: %v", err)
+		}
+
+		if answer.Data.Content == nil || !strings.Contains(*answer.Data.Content, "HELLO") {
+			t.Errorf("Expected answer to contain 'HELLO', got %v", answer.Data.Content)
+		}
+
+		// Should have received a custom-tool permission request
+		mu.Lock()
+		customToolReqs := 0
+		for _, req := range permissionRequests {
+			if req.Kind == "custom-tool" {
+				customToolReqs++
+			}
+		}
+		mu.Unlock()
+		if customToolReqs == 0 {
+			t.Errorf("Expected at least one custom-tool permission request, got none")
+		}
+	})
+
+	t.Run("denies custom tool when permission denied", func(t *testing.T) {
+		ctx.ConfigureForTest(t)
+
+		type EncryptParams struct {
+			Input string `json:"input" jsonschema:"String to encrypt"`
+		}
+
+		toolHandlerCalled := false
+
+		session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
+			Tools: []copilot.Tool{
+				copilot.DefineTool("encrypt_string", "Encrypts a string",
+					func(params EncryptParams, inv copilot.ToolInvocation) (string, error) {
+						toolHandlerCalled = true
+						return strings.ToUpper(params.Input), nil
+					}),
+			},
+			OnPermissionRequest: func(request copilot.PermissionRequest, invocation copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) {
+				return copilot.PermissionRequestResult{Kind: "denied-interactively-by-user"}, nil
+			},
+		})
+		if err != nil {
+			t.Fatalf("Failed to create session: %v", err)
+		}
+
+		_, err = session.Send(t.Context(), copilot.MessageOptions{Prompt: "Use encrypt_string to encrypt this string: Hello"})
+		if err != nil {
+			t.Fatalf("Failed to send message: %v", err)
+		}
+
+		_, err = testharness.GetFinalAssistantMessage(t.Context(), session)
+		if err != nil {
+			t.Fatalf("Failed to get assistant message: %v", err)
+		}
+
+		if toolHandlerCalled {
+			t.Errorf("Tool handler should NOT have been called since permission was denied")
+		}
+	})
 }
diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts
index c016edff..3a0ccbce 100644
--- a/nodejs/src/types.ts
+++ b/nodejs/src/types.ts
@@ -211,7 +211,7 @@ export type SystemMessageConfig = SystemMessageAppendConfig | SystemMessageRepla
  * Permission request types from the server
  */
 export interface PermissionRequest {
-    kind: "shell" | "write" | "mcp" | "read" | "url";
+    kind: "shell" | "write" | "mcp" | "read" | "url" | "custom-tool";
     toolCallId?: string;
     [key: string]: unknown;
 }
diff --git a/nodejs/test/e2e/tools.test.ts b/nodejs/test/e2e/tools.test.ts
index a6ad0c04..8db079b3 100644
--- a/nodejs/test/e2e/tools.test.ts
+++ b/nodejs/test/e2e/tools.test.ts
@@ -7,6 +7,7 @@ import { join } from "path";
 import { assert, describe, expect, it } from "vitest";
 import { z } from "zod";
 import { defineTool, approveAll } from "../../src/index.js";
+import type { PermissionRequest, PermissionRequestResult } from "../../src/index.js";
 import { createSdkTestContext } from "./harness/sdkTestContext";
 
 describe("Custom tools", async () => {
@@ -36,6 +37,7 @@ describe("Custom tools", async () => {
                     handler: ({ input }) => input.toUpperCase(),
                 }),
             ],
+            onPermissionRequest: approveAll,
         });
 
         const assistantMessage = await session.sendAndWait({
@@ -55,6 +57,7 @@ describe("Custom tools", async () => {
                     },
                 }),
             ],
+            onPermissionRequest: approveAll,
         });
 
         const answer = await session.sendAndWait({
@@ -111,6 +114,7 @@ describe("Custom tools", async () => {
                     },
                 }),
             ],
+            onPermissionRequest: approveAll,
         });
 
         const assistantMessage = await session.sendAndWait({
@@ -127,4 +131,63 @@ describe("Custom tools", async () => {
         expect(responseContent.replace(/,/g, "")).toContain("135460");
         expect(responseContent.replace(/,/g, "")).toContain("204356");
     });
+
+    it("invokes custom tool with permission handler", async () => {
+        const permissionRequests: PermissionRequest[] = [];
+
+        const session = await client.createSession({
+            tools: [
+                defineTool("encrypt_string", {
+                    description: "Encrypts a string",
+                    parameters: z.object({
+                        input: z.string().describe("String to encrypt"),
+                    }),
+                    handler: ({ input }) => input.toUpperCase(),
+                }),
+            ],
+            onPermissionRequest: (request) => {
+                permissionRequests.push(request);
+                return { kind: "approved" };
+            },
+        });
+
+        const assistantMessage = await session.sendAndWait({
+            prompt: "Use encrypt_string to encrypt this string: Hello",
+        });
+        expect(assistantMessage?.data.content).toContain("HELLO");
+
+        // Should have received a custom-tool permission request
+        const customToolRequests = permissionRequests.filter((req) => req.kind === "custom-tool");
+        expect(customToolRequests.length).toBeGreaterThan(0);
+        expect(customToolRequests[0].toolName).toBe("encrypt_string");
+    });
+
+    it("denies custom tool when permission denied", async () => {
+        let toolHandlerCalled = false;
+
+        const session = await client.createSession({
+            tools: [
+                defineTool("encrypt_string", {
+                    description: "Encrypts a string",
+                    parameters: z.object({
+                        input: z.string().describe("String to encrypt"),
+                    }),
+                    handler: ({ input }) => {
+                        toolHandlerCalled = true;
+                        return input.toUpperCase();
+                    },
+                }),
+            ],
+            onPermissionRequest: () => {
+                return { kind: "denied-interactively-by-user" };
+            },
+        });
+
+        await session.sendAndWait({
+            prompt: "Use encrypt_string to encrypt this string: Hello",
+        });
+
+        // The tool handler should NOT have been called since permission was denied
+        expect(toolHandlerCalled).toBe(false);
+    });
 });
diff --git a/python/copilot/types.py b/python/copilot/types.py
index e8939977..142aee47 100644
--- a/python/copilot/types.py
+++ b/python/copilot/types.py
@@ -169,7 +169,7 @@ class SystemMessageReplaceConfig(TypedDict):
 class PermissionRequest(TypedDict, total=False):
     """Permission request from the server"""
 
-    kind: Literal["shell", "write", "mcp", "read", "url"]
+    kind: Literal["shell", "write", "mcp", "read", "url", "custom-tool"]
     toolCallId: str
     # Additional fields vary by kind
 
diff --git a/python/e2e/test_tools.py b/python/e2e/test_tools.py
index 485998e0..7e66441a 100644
--- a/python/e2e/test_tools.py
+++ b/python/e2e/test_tools.py
@@ -132,3 +132,57 @@ def db_query(params: DbQueryParams, invocation: ToolInvocation) -> list[City]:
         assert "San Lorenzo" in response_content
         assert "135460" in response_content.replace(",", "")
         assert "204356" in response_content.replace(",", "")
+
+    async def test_invokes_custom_tool_with_permission_handler(self, ctx: E2ETestContext):
+        class EncryptParams(BaseModel):
+            input: str = Field(description="String to encrypt")
+
+        @define_tool("encrypt_string", description="Encrypts a string")
+        def encrypt_string(params: EncryptParams, invocation: ToolInvocation) -> str:
+            return params.input.upper()
+
+        permission_requests = []
+
+        def on_permission_request(request, invocation):
+            permission_requests.append(request)
+            return {"kind": "approved"}
+
+        session = await ctx.client.create_session({
+            "tools": [encrypt_string],
+            "on_permission_request": on_permission_request,
+        })
+
+        await session.send({"prompt": "Use encrypt_string to encrypt this string: Hello"})
+        assistant_message = await get_final_assistant_message(session)
+        assert "HELLO" in assistant_message.data.content
+
+        # Should have received a custom-tool permission request
+        custom_tool_requests = [r for r in permission_requests if r.get("kind") == "custom-tool"]
+        assert len(custom_tool_requests) > 0
+        assert custom_tool_requests[0].get("toolName") == "encrypt_string"
+
+    async def test_denies_custom_tool_when_permission_denied(self, ctx: E2ETestContext):
+        tool_handler_called = False
+
+        class EncryptParams(BaseModel):
+            input: str = Field(description="String to encrypt")
+
+        @define_tool("encrypt_string", description="Encrypts a string")
+        def encrypt_string(params: EncryptParams, invocation: ToolInvocation) -> str:
+            nonlocal tool_handler_called
+            tool_handler_called = True
+            return params.input.upper()
+
+        def on_permission_request(request, invocation):
+            return {"kind": "denied-interactively-by-user"}
+
+        session = await ctx.client.create_session({
+            "tools": [encrypt_string],
+            "on_permission_request": on_permission_request,
+        })
+
+        await session.send({"prompt": "Use encrypt_string to encrypt this string: Hello"})
+        await get_final_assistant_message(session)
+
+        # The tool handler should NOT have been called since permission was denied
+        assert not tool_handler_called
diff --git a/test/snapshots/tools/denies_custom_tool_when_permission_denied.yaml b/test/snapshots/tools/denies_custom_tool_when_permission_denied.yaml
new file mode 100644
index 00000000..47f9286e
--- /dev/null
+++ b/test/snapshots/tools/denies_custom_tool_when_permission_denied.yaml
@@ -0,0 +1,15 @@
+models:
+  - claude-sonnet-4.5
+conversations:
+  - messages:
+      - role: system
+        content: ${system}
+      - role: user
+        content: "Use encrypt_string to encrypt this string: Hello"
+      - role: assistant
+        tool_calls:
+          - id: toolcall_0
+            type: function
+            function:
+              name: encrypt_string
+              arguments: '{"input":"Hello"}'
diff --git a/test/snapshots/tools/invokes_custom_tool_with_permission_handler.yaml b/test/snapshots/tools/invokes_custom_tool_with_permission_handler.yaml
new file mode 100644
index 00000000..5b046d4c
--- /dev/null
+++ b/test/snapshots/tools/invokes_custom_tool_with_permission_handler.yaml
@@ -0,0 +1,20 @@
+models:
+  - claude-sonnet-4.5
+conversations:
+  - messages:
+      - role: system
+        content: ${system}
+      - role: user
+        content: "Use encrypt_string to encrypt this string: Hello"
+      - role: assistant
+        tool_calls:
+          - id: toolcall_0
+            type: function
+            function:
+              name: encrypt_string
+              arguments: '{"input":"Hello"}'
+      - role: tool
+        tool_call_id: toolcall_0
+        content: HELLO
+      - role: assistant
+        content: "The encrypted result is: **HELLO**"

From b57d0d50d753538f5cd5f17fb87c386039509cb0 Mon Sep 17 00:00:00 2001
From: Steve Sanderson <SteveSandersonMS@users.noreply.github.com>
Date: Tue, 24 Feb 2026 11:58:50 +0000
Subject: [PATCH 2/5] Address PR review: remove unused import, add toolName
 verification to Go and .NET tests

- Remove unused PermissionRequestResult import from Node.js test
- Add toolName assertion in Go test for cross-SDK parity
- Add toolName assertion in .NET test for cross-SDK parity

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 dotnet/test/ToolsTests.cs     | 9 +++++++--
 go/internal/e2e/tools_test.go | 3 +++
 nodejs/test/e2e/tools.test.ts | 2 +-
 3 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/dotnet/test/ToolsTests.cs b/dotnet/test/ToolsTests.cs
index 14f42616..c6449ec8 100644
--- a/dotnet/test/ToolsTests.cs
+++ b/dotnet/test/ToolsTests.cs
@@ -5,6 +5,7 @@
 using GitHub.Copilot.SDK.Test.Harness;
 using Microsoft.Extensions.AI;
 using System.ComponentModel;
+using System.Linq;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 using Xunit;
@@ -206,8 +207,12 @@ await session.SendAsync(new MessageOptions
         Assert.NotNull(assistantMessage);
         Assert.Contains("HELLO", assistantMessage!.Data.Content ?? string.Empty);
 
-        // Should have received a custom-tool permission request
-        Assert.Contains(permissionRequests, r => r.Kind == "custom-tool");
+        // Should have received a custom-tool permission request with the correct tool name
+        var customToolRequest = permissionRequests.FirstOrDefault(r => r.Kind == "custom-tool");
+        Assert.NotNull(customToolRequest);
+        Assert.True(customToolRequest!.ExtensionData?.ContainsKey("toolName") ?? false);
+        var toolName = ((JsonElement)customToolRequest.ExtensionData!["toolName"]).GetString();
+        Assert.Equal("encrypt_string", toolName);
 
         [Description("Encrypts a string")]
         static string EncryptStringForPermission([Description("String to encrypt")] string input)
diff --git a/go/internal/e2e/tools_test.go b/go/internal/e2e/tools_test.go
index d6158248..b746f566 100644
--- a/go/internal/e2e/tools_test.go
+++ b/go/internal/e2e/tools_test.go
@@ -315,6 +315,9 @@ func TestTools(t *testing.T) {
 		for _, req := range permissionRequests {
 			if req.Kind == "custom-tool" {
 				customToolReqs++
+				if toolName, ok := req.Extra["toolName"].(string); !ok || toolName != "encrypt_string" {
+					t.Errorf("Expected toolName 'encrypt_string', got '%v'", req.Extra["toolName"])
+				}
 			}
 		}
 		mu.Unlock()
diff --git a/nodejs/test/e2e/tools.test.ts b/nodejs/test/e2e/tools.test.ts
index 8db079b3..feab2fbf 100644
--- a/nodejs/test/e2e/tools.test.ts
+++ b/nodejs/test/e2e/tools.test.ts
@@ -7,7 +7,7 @@ import { join } from "path";
 import { assert, describe, expect, it } from "vitest";
 import { z } from "zod";
 import { defineTool, approveAll } from "../../src/index.js";
-import type { PermissionRequest, PermissionRequestResult } from "../../src/index.js";
+import type { PermissionRequest } from "../../src/index.js";
 import { createSdkTestContext } from "./harness/sdkTestContext";
 
 describe("Custom tools", async () => {

From c4d1382568618b5131f6b557fc2ece1e5f5badbd Mon Sep 17 00:00:00 2001
From: Steve Sanderson <SteveSandersonMS@users.noreply.github.com>
Date: Wed, 25 Feb 2026 00:26:02 +0000
Subject: [PATCH 3/5] Formatting

---
 python/e2e/test_tools.py | 20 ++++++++++++--------
 1 file changed, 12 insertions(+), 8 deletions(-)

diff --git a/python/e2e/test_tools.py b/python/e2e/test_tools.py
index 7e66441a..e4a9f5f0 100644
--- a/python/e2e/test_tools.py
+++ b/python/e2e/test_tools.py
@@ -147,10 +147,12 @@ def on_permission_request(request, invocation):
             permission_requests.append(request)
             return {"kind": "approved"}
 
-        session = await ctx.client.create_session({
-            "tools": [encrypt_string],
-            "on_permission_request": on_permission_request,
-        })
+        session = await ctx.client.create_session(
+            {
+                "tools": [encrypt_string],
+                "on_permission_request": on_permission_request,
+            }
+        )
 
         await session.send({"prompt": "Use encrypt_string to encrypt this string: Hello"})
         assistant_message = await get_final_assistant_message(session)
@@ -176,10 +178,12 @@ def encrypt_string(params: EncryptParams, invocation: ToolInvocation) -> str:
         def on_permission_request(request, invocation):
             return {"kind": "denied-interactively-by-user"}
 
-        session = await ctx.client.create_session({
-            "tools": [encrypt_string],
-            "on_permission_request": on_permission_request,
-        })
+        session = await ctx.client.create_session(
+            {
+                "tools": [encrypt_string],
+                "on_permission_request": on_permission_request,
+            }
+        )
 
         await session.send({"prompt": "Use encrypt_string to encrypt this string: Hello"})
         await get_final_assistant_message(session)

From 4ff8ce20a24f2640020741dcf3d9131844c19069 Mon Sep 17 00:00:00 2001
From: Steve Sanderson <SteveSandersonMS@users.noreply.github.com>
Date: Wed, 25 Feb 2026 00:27:11 +0000
Subject: [PATCH 4/5] Fix rebase issue

---
 go/internal/e2e/tools_test.go | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/go/internal/e2e/tools_test.go b/go/internal/e2e/tools_test.go
index b746f566..e5b93fa2 100644
--- a/go/internal/e2e/tools_test.go
+++ b/go/internal/e2e/tools_test.go
@@ -63,7 +63,6 @@ func TestTools(t *testing.T) {
 						return strings.ToUpper(params.Input), nil
 					}),
 			},
-			OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
 		})
 		if err != nil {
 			t.Fatalf("Failed to create session: %v", err)
@@ -97,7 +96,6 @@ func TestTools(t *testing.T) {
 						return nil, errors.New("Melbourne")
 					}),
 			},
-			OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
 		})
 		if err != nil {
 			t.Fatalf("Failed to create session: %v", err)
@@ -216,7 +214,6 @@ func TestTools(t *testing.T) {
 						}, nil
 					}),
 			},
-			OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
 		})
 		if err != nil {
 			t.Fatalf("Failed to create session: %v", err)

From 7c46ece3da71b042f0b5056e3dbd371929e6af68 Mon Sep 17 00:00:00 2001
From: Steve Sanderson <SteveSandersonMS@users.noreply.github.com>
Date: Wed, 25 Feb 2026 00:33:52 +0000
Subject: [PATCH 5/5] Go fix

---
 go/types.go | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/go/types.go b/go/types.go
index f3f299ed..225cc126 100644
--- a/go/types.go
+++ b/go/types.go
@@ -106,6 +106,32 @@ type PermissionRequest struct {
 	Extra      map[string]any `json:"-"` // Additional fields vary by kind
 }
 
+// UnmarshalJSON implements custom JSON unmarshaling for PermissionRequest
+// to capture additional fields (varying by kind) into the Extra map.
+func (p *PermissionRequest) UnmarshalJSON(data []byte) error {
+	// Unmarshal known fields via an alias to avoid infinite recursion
+	type Alias PermissionRequest
+	var alias Alias
+	if err := json.Unmarshal(data, &alias); err != nil {
+		return err
+	}
+	*p = PermissionRequest(alias)
+
+	// Unmarshal all fields into a generic map
+	var raw map[string]any
+	if err := json.Unmarshal(data, &raw); err != nil {
+		return err
+	}
+
+	// Remove known fields, keep the rest as Extra
+	delete(raw, "kind")
+	delete(raw, "toolCallId")
+	if len(raw) > 0 {
+		p.Extra = raw
+	}
+	return nil
+}
+
 // PermissionRequestResult represents the result of a permission request
 type PermissionRequestResult struct {
 	Kind  string `json:"kind"`
