diff --git a/python/packages/github_copilot/agent_framework_github_copilot/_agent.py b/python/packages/github_copilot/agent_framework_github_copilot/_agent.py index 2f7b963cb75..c58e5edf711 100644 --- a/python/packages/github_copilot/agent_framework_github_copilot/_agent.py +++ b/python/packages/github_copilot/agent_framework_github_copilot/_agent.py @@ -77,6 +77,7 @@ Attachment, BlobAttachment, MCPServerConfig, + PermissionInvocation, PermissionRequestResult, PreToolUseHandler, PreToolUseHookOutput, @@ -110,14 +111,24 @@ DEFAULT_TIMEOUT_SECONDS: float = 60.0 """Default timeout in seconds for Copilot requests.""" +_PermissionHandlerContext = Any +"""Compatibility context accepted by permission handlers across SDK versions.""" + PermissionHandlerType = Callable[ - [PermissionRequest, dict[str, str]], "PermissionRequestResult | Awaitable[PermissionRequestResult]" + [PermissionRequest, _PermissionHandlerContext], + "PermissionRequestResult | Awaitable[PermissionRequestResult]", ] """Type for permission request handlers. Supports both sync and async callbacks.""" -AsyncPermissionHandlerType = Callable[[PermissionRequest, dict[str, str]], "Awaitable[PermissionRequestResult]"] +AsyncPermissionHandlerType = Callable[ + [PermissionRequest, _PermissionHandlerContext], "Awaitable[PermissionRequestResult]" +] """Type for permission request handlers that are always asynchronous.""" +_SdkAsyncPermissionHandlerType = Callable[ + [PermissionRequest, PermissionInvocation], "Awaitable[PermissionRequestResult]" +] + FunctionApprovalCallback = Callable[[Content], "bool | Awaitable[bool]"] """Deprecated approval callback for ``FunctionTool`` instances declared with @@ -171,7 +182,7 @@ async def _resolve_function_approval( def _deny_all_permissions( _request: PermissionRequest, - _invocation: dict[str, str], + _invocation: _PermissionHandlerContext, ) -> PermissionRequestResult: """Default permission handler that denies all requests.""" return PermissionDecisionUserNotAvailable() @@ -322,7 +333,7 @@ def _normalize_permission_decision( return PermissionDecisionApproveForSession(approval=approval) -def _with_normalized_permission_decisions(handler: PermissionHandlerType) -> AsyncPermissionHandlerType: +def _with_normalized_permission_decisions(handler: PermissionHandlerType) -> _SdkAsyncPermissionHandlerType: """Wrap a permission handler so its decisions are normalized before reaching the SDK. Exceptions raised by ``handler`` deliberately propagate: the SDK already catches them @@ -335,8 +346,10 @@ def _with_normalized_permission_decisions(handler: PermissionHandlerType) -> Asy An async handler delegating to ``handler`` and normalizing its result. """ - async def normalized_handler(request: PermissionRequest, invocation: dict[str, str]) -> PermissionRequestResult: - result = handler(request, invocation) + async def normalized_handler( + request: PermissionRequest, invocation: PermissionInvocation + ) -> PermissionRequestResult: + result = handler(request, cast(PermissionInvocation, dict(invocation))) if inspect.isawaitable(result): result = await result return _normalize_permission_decision(result, request) @@ -1468,7 +1481,10 @@ def _build_session_kwargs( if not kwargs.get("model"): kwargs["model"] = self._settings.get("model") or None kwargs["on_permission_request"] = _with_normalized_permission_decisions( - opts.get("on_permission_request") or self._permission_handler or _deny_all_permissions + cast( + PermissionHandlerType, + opts.get("on_permission_request") or self._permission_handler or _deny_all_permissions, + ) ) kwargs["hooks"] = self._build_session_hooks(all_tools, kwargs) diff --git a/python/packages/github_copilot/pyproject.toml b/python/packages/github_copilot/pyproject.toml index ef660ec3032..c9205a397d8 100644 --- a/python/packages/github_copilot/pyproject.toml +++ b/python/packages/github_copilot/pyproject.toml @@ -23,7 +23,7 @@ classifiers = [ ] dependencies = [ "agent-framework-core>=1.15.0,<2", - "github-copilot-sdk==1.0.2; python_version >= '3.11'", + "github-copilot-sdk==1.0.11; python_version >= '3.11'", ] [tool.uv] diff --git a/python/packages/github_copilot/tests/test_github_copilot_agent.py b/python/packages/github_copilot/tests/test_github_copilot_agent.py index 6e17db2f733..3643ae9e591 100644 --- a/python/packages/github_copilot/tests/test_github_copilot_agent.py +++ b/python/packages/github_copilot/tests/test_github_copilot_agent.py @@ -28,7 +28,7 @@ tool, ) from agent_framework.exceptions import AgentException -from copilot.session import PermissionHandler, PreToolUseHookInput +from copilot.session import PermissionHandler, PermissionInvocation, PreToolUseHookInput from copilot.session_events import ( AssistantUsageData, Data, @@ -1329,7 +1329,7 @@ async def test_resume_session_includes_tools_and_permissions( from copilot.session import PermissionDecisionApproveOnce, PermissionRequestResult from copilot.session_events import PermissionRequest - def my_handler(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: + def my_handler(request: PermissionRequest, context: PermissionInvocation) -> PermissionRequestResult: return PermissionDecisionApproveOnce() def my_tool(arg: str) -> str: @@ -2603,7 +2603,7 @@ def test_permission_handler_set_when_provided(self) -> None: from copilot.session import PermissionDecisionApproveOnce, PermissionRequestResult from copilot.session_events import PermissionRequest - def approve_shell(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: + def approve_shell(request: PermissionRequest, context: PermissionInvocation) -> PermissionRequestResult: if request.kind == "shell": return PermissionDecisionApproveOnce() return PermissionDecisionDeniedInteractivelyByUser() @@ -2621,7 +2621,7 @@ async def test_session_config_includes_permission_handler( from copilot.session import PermissionDecisionApproveOnce, PermissionRequestResult from copilot.session_events import PermissionRequest - def approve_shell_read(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: + def approve_shell_read(request: PermissionRequest, context: PermissionInvocation) -> PermissionRequestResult: if request.kind in ("shell", "read"): return PermissionDecisionApproveOnce() return PermissionDecisionDeniedInteractivelyByUser() @@ -2943,6 +2943,24 @@ async def async_handler(_request: Any, _invocation: Any) -> Any: assert isinstance(result, PermissionDecisionApproveForSession) assert isinstance(result.approval, PermissionDecisionApproveForSessionApprovalCommands) + async def test_legacy_dict_permission_handlers_are_supported(self) -> None: + """The wrapper continues to support handlers typed for the legacy dictionary context.""" + from copilot.generated.rpc import PermissionDecisionApproveOnce + from copilot.session_events import PermissionRequest + + received_context: dict[str, str] = {} + + def legacy_handler(request: PermissionRequest, context: dict[str, str]) -> Any: + received_context.update(context) + return PermissionDecisionApproveOnce() + + from agent_framework_github_copilot._agent import _with_normalized_permission_decisions + + handler = _with_normalized_permission_decisions(legacy_handler) # type: ignore[arg-type] + await handler(shell_request(["ls"]), {"session_id": "test-session"}) + + assert received_context == {"session_id": "test-session"} + async def test_handler_exceptions_propagate(self) -> None: """Handler failures must keep reaching the SDK, which denies the request.""" from agent_framework_github_copilot._agent import _with_normalized_permission_decisions diff --git a/python/samples/02-agents/providers/github_copilot/github_copilot_with_file_operations.py b/python/samples/02-agents/providers/github_copilot/github_copilot_with_file_operations.py index 7f363add9a4..6fac5d3eb82 100644 --- a/python/samples/02-agents/providers/github_copilot/github_copilot_with_file_operations.py +++ b/python/samples/02-agents/providers/github_copilot/github_copilot_with_file_operations.py @@ -15,11 +15,11 @@ from agent_framework.github import GitHubCopilotAgent, GitHubCopilotOptions from copilot.generated.rpc import PermissionDecisionDeniedInteractivelyByUser -from copilot.session import PermissionHandler, PermissionRequestResult +from copilot.session import PermissionHandler, PermissionInvocation, PermissionRequestResult from copilot.session_events import PermissionRequest -async def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: +async def prompt_permission(request: PermissionRequest, context: PermissionInvocation) -> PermissionRequestResult: """Permission handler that prompts the user for approval.""" print(f"\n[Permission Request: {request.kind}]") response = (await asyncio.to_thread(input, "Approve? (y/n): ")).strip().lower() diff --git a/python/samples/02-agents/providers/github_copilot/github_copilot_with_function_approval.py b/python/samples/02-agents/providers/github_copilot/github_copilot_with_function_approval.py index 0502be7ea18..a191fab6281 100644 --- a/python/samples/02-agents/providers/github_copilot/github_copilot_with_function_approval.py +++ b/python/samples/02-agents/providers/github_copilot/github_copilot_with_function_approval.py @@ -35,6 +35,7 @@ from copilot.generated.rpc import PermissionDecisionReject from copilot.session import ( PermissionHandler, + PermissionInvocation, PermissionRequestResult, PreToolUseHookInput, PreToolUseHookOutput, @@ -63,13 +64,13 @@ def get_weather_detail(location: Annotated[str, "The city and state, e.g. San Fr ) -def approve_all_requests(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: +def approve_all_requests(request: PermissionRequest, context: PermissionInvocation) -> PermissionRequestResult: """Permission handler that approves every request, including the gated tool.""" print(f"\n [Permission requested: {request.kind}] -> approved") return PermissionHandler.approve_all(request, context) -def deny_all_requests(request: PermissionRequest, _context: dict[str, str]) -> PermissionRequestResult: +def deny_all_requests(request: PermissionRequest, _context: PermissionInvocation) -> PermissionRequestResult: """Permission handler that denies every request.""" print(f"\n [Permission requested: {request.kind}] -> denied") return PermissionDecisionReject(feedback="Denied by the operator's policy.") diff --git a/python/samples/02-agents/providers/github_copilot/github_copilot_with_multiple_permissions.py b/python/samples/02-agents/providers/github_copilot/github_copilot_with_multiple_permissions.py index 4e375e181a4..60d6377cd98 100644 --- a/python/samples/02-agents/providers/github_copilot/github_copilot_with_multiple_permissions.py +++ b/python/samples/02-agents/providers/github_copilot/github_copilot_with_multiple_permissions.py @@ -20,11 +20,11 @@ import asyncio from agent_framework.github import GitHubCopilotAgent, GitHubCopilotOptions -from copilot.session import PermissionHandler, PermissionRequestResult +from copilot.session import PermissionHandler, PermissionInvocation, PermissionRequestResult from copilot.session_events import PermissionRequest -def approve_and_log(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: +def approve_and_log(request: PermissionRequest, context: PermissionInvocation) -> PermissionRequestResult: """Permission handler that auto-approves and logs each permission kind.""" print(f" [Permission: {request.kind}]", flush=True) return PermissionHandler.approve_all(request, context) diff --git a/python/samples/02-agents/providers/github_copilot/github_copilot_with_shell.py b/python/samples/02-agents/providers/github_copilot/github_copilot_with_shell.py index 0cd6ba3728a..50a84917d6b 100644 --- a/python/samples/02-agents/providers/github_copilot/github_copilot_with_shell.py +++ b/python/samples/02-agents/providers/github_copilot/github_copilot_with_shell.py @@ -15,11 +15,11 @@ from agent_framework.github import GitHubCopilotAgent, GitHubCopilotOptions from copilot.generated.rpc import PermissionDecisionUserNotAvailable -from copilot.session import PermissionHandler, PermissionRequestResult +from copilot.session import PermissionHandler, PermissionInvocation, PermissionRequestResult from copilot.session_events import PermissionRequest -def approve_and_log(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: +def approve_and_log(request: PermissionRequest, context: PermissionInvocation) -> PermissionRequestResult: """Permission handler that approves only shell commands and logs them.""" if request.kind == "shell": print(f"\n [Permission: {request.kind}]", flush=True) diff --git a/python/samples/02-agents/providers/github_copilot/github_copilot_with_url.py b/python/samples/02-agents/providers/github_copilot/github_copilot_with_url.py index eb3edc5296b..1d57a2b0765 100644 --- a/python/samples/02-agents/providers/github_copilot/github_copilot_with_url.py +++ b/python/samples/02-agents/providers/github_copilot/github_copilot_with_url.py @@ -15,11 +15,11 @@ from agent_framework.github import GitHubCopilotAgent, GitHubCopilotOptions from copilot.generated.rpc import PermissionDecisionUserNotAvailable -from copilot.session import PermissionHandler, PermissionRequestResult +from copilot.session import PermissionHandler, PermissionInvocation, PermissionRequestResult from copilot.session_events import PermissionRequest -def approve_and_log(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: +def approve_and_log(request: PermissionRequest, context: PermissionInvocation) -> PermissionRequestResult: """Permission handler that approves only URL requests and logs them.""" if request.kind == "url": print(f"\n [Permission: {request.kind}]", flush=True) diff --git a/python/uv.lock b/python/uv.lock index b612dd2b739..6a3b1a24c00 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -692,7 +692,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "agent-framework-core", editable = "packages/core" }, - { name = "github-copilot-sdk", marker = "python_full_version >= '3.11'", specifier = "==1.0.2" }, + { name = "github-copilot-sdk", marker = "python_full_version >= '3.11'", specifier = "==1.0.11" }, ] [[package]] @@ -2835,19 +2835,15 @@ wheels = [ [[package]] name = "github-copilot-sdk" -version = "1.0.2" +version = "1.0.11" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "httpx", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "python-dateutil", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/2c/3d3ecfe500c0ba7d3127737b1aa22f19ff1a19e6e86360bfdca3f02a2c09/github_copilot_sdk-1.0.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:856dfc8370f36f6efd8a2aa1dd40f82c1a6d0573d0577eaff1f6affb73ed29ad", size = 97329153, upload-time = "2026-06-18T00:56:20.653Z" }, - { url = "https://files.pythonhosted.org/packages/3d/ab/d4ab9320e50a1381d436401f75f8ad54fa57541324451ef2a96db6258464/github_copilot_sdk-1.0.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c78c610c3fd7be82ab69a76fb21e04eb89482072e07e31ad1ca63c893aad0c6d", size = 90809903, upload-time = "2026-06-18T00:56:25.128Z" }, - { url = "https://files.pythonhosted.org/packages/24/33/880d681e5d661f8c66ac02c0b17091538ec716b62534a0d08859d48f7f99/github_copilot_sdk-1.0.2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:32a844e7fa9644614f9092eeca853368debfed527baaaebfd1a4d4ee89cbdcbd", size = 99564035, upload-time = "2026-06-18T00:56:29.401Z" }, - { url = "https://files.pythonhosted.org/packages/d5/44/ce7485fada7a96ece22d7d0a0a18804d09dca2911b544f2fd90f4d7c02ff/github_copilot_sdk-1.0.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:dd1faaaf896a7f97d9c36a1a40e968132e141019ce5a11c4666b089b7d6ea8cd", size = 97202424, upload-time = "2026-06-18T00:56:33.598Z" }, - { url = "https://files.pythonhosted.org/packages/76/90/755c9908a9bbdeb76ad3361d9e23c89ce7520c673745166a5ee2103443d1/github_copilot_sdk-1.0.2-py3-none-win_amd64.whl", hash = "sha256:d932666bba33a840421a183079141ea6b7a78ce4f84d30fb1ad035ee00b464e4", size = 93640286, upload-time = "2026-06-18T00:56:37.73Z" }, - { url = "https://files.pythonhosted.org/packages/f5/74/22204ba7547f4ea849993920240325cdb9851df0177cc808cb20ca04ef5a/github_copilot_sdk-1.0.2-py3-none-win_arm64.whl", hash = "sha256:7a7d99a2fb1c2fb1c05753110961fb99489201fc91a16be5297ef1c2ca2333f2", size = 92382516, upload-time = "2026-06-18T00:56:41.148Z" }, + { url = "https://files.pythonhosted.org/packages/67/ac/175cbb71fe637d963a885248a421040c9d500ea6390ed3b88bc68ecf51ca/github_copilot_sdk-1.0.11-py3-none-any.whl", hash = "sha256:6f664c7b843c34ab5a7455f7effb4d339eae75969d2b72f43c2e6214c9c637dc", size = 486719, upload-time = "2026-08-14T16:12:20.01Z" }, ] [[package]]