From c8dd7ed7d82f2cf13773716cb69868ebab27e2b4 Mon Sep 17 00:00:00 2001 From: Giles Odigwe Date: Tue, 2 Jun 2026 16:42:37 -0700 Subject: [PATCH 1/4] Python: Upgrade github-copilot-sdk to v1.0.0 (stable) Upgrade agent-framework-github-copilot from github-copilot-sdk 1.0.0b2 to the stable 1.0.0 release, adapting to all breaking API changes. Source changes (_agent.py): - SubprocessConfig removed: use RuntimeConnection.for_stdio(path=...) + CopilotClient kwargs (connection, log_level, base_directory) - Import paths: copilot.generated.session_events -> copilot.session_events - Settings: copilot_home -> base_directory (env GITHUB_COPILOT_BASE_DIRECTORY) - Default deny handler: PermissionDecisionUserNotAvailable() (from copilot.generated.rpc) Test changes: - Updated imports and client-construction assertions (kwargs-based) - Permission handler tests use concrete decision types (PermissionDecisionApproveOnce, PermissionDecisionDeniedInteractivelyByUser) Sample changes: - Permission handlers use PermissionHandler.approve_all or sync approve_and_log pattern (v1.0.0 protocol v3 dispatch is incompatible with blocking input() in permission handlers) - Function approval sample uses asyncio.to_thread for interactive prompts - Simplified imports across all samples Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../agent_framework_github_copilot/_agent.py | 31 ++-- python/packages/github_copilot/pyproject.toml | 2 +- .../tests/test_github_copilot_agent.py | 60 ++++---- .../providers/github_copilot/README.md | 2 +- .../github_copilot/github_copilot_basic.py | 22 +-- .../github_copilot_with_file_operations.py | 20 ++- .../github_copilot_with_function_approval.py | 51 ++++--- ...ub_copilot_with_instruction_directories.py | 17 +-- .../github_copilot/github_copilot_with_mcp.py | 15 +- ...ithub_copilot_with_multiple_permissions.py | 32 ++--- .../github_copilot_with_session.py | 24 +--- .../github_copilot_with_shell.py | 23 ++- .../github_copilot/github_copilot_with_url.py | 23 ++- python/uv.lock | 133 +++++++++++++++--- 14 files changed, 248 insertions(+), 207 deletions(-) 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 0fc9c9dcf61..d68e8238014 100644 --- a/python/packages/github_copilot/agent_framework_github_copilot/_agent.py +++ b/python/packages/github_copilot/agent_framework_github_copilot/_agent.py @@ -37,9 +37,10 @@ from agent_framework.observability import AgentTelemetryLayer try: - from copilot import CopilotClient, CopilotSession, SubprocessConfig - from copilot.generated.session_events import PermissionRequest, SessionEvent, SessionEventType + from copilot import CopilotClient, CopilotSession, RuntimeConnection + from copilot.generated.rpc import PermissionDecisionUserNotAvailable from copilot.session import MCPServerConfig, PermissionRequestResult, ProviderConfig, SystemMessageConfig + from copilot.session_events import PermissionRequest, SessionEvent, SessionEventType from copilot.tools import Tool as CopilotTool from copilot.tools import ToolInvocation, ToolResult except ImportError as _copilot_import_error: @@ -121,7 +122,7 @@ def _deny_all_permissions( _invocation: dict[str, str], ) -> PermissionRequestResult: """Default permission handler that denies all requests.""" - return PermissionRequestResult() + return PermissionDecisionUserNotAvailable() class GitHubCopilotSettings(TypedDict, total=False): @@ -140,9 +141,9 @@ class GitHubCopilotSettings(TypedDict, total=False): Can be set via environment variable GITHUB_COPILOT_TIMEOUT. log_level: CLI log level. Can be set via environment variable GITHUB_COPILOT_LOG_LEVEL. - copilot_home: Directory where the CLI stores session state, configuration, + base_directory: Directory where the CLI stores session state, configuration, and other persistent data. Can be set via environment variable - GITHUB_COPILOT_COPILOT_HOME. Defaults to ~/.copilot when not set. + GITHUB_COPILOT_BASE_DIRECTORY. Defaults to ~/.copilot when not set. Only applicable when the SDK spawns the CLI process (ignored when connecting to an external server via a pre-configured client). """ @@ -151,7 +152,7 @@ class GitHubCopilotSettings(TypedDict, total=False): model: str | None timeout: float | None log_level: str | None - copilot_home: str | None + base_directory: str | None class GitHubCopilotOptions(TypedDict, total=False): @@ -314,7 +315,7 @@ def __init__( provider: ProviderConfig | None = opts.pop("provider", None) instruction_directories: list[str] | None = opts.pop("instruction_directories", None) on_function_approval: FunctionApprovalCallback | None = opts.pop("on_function_approval", None) - copilot_home = opts.pop("copilot_home", None) + base_directory = opts.pop("base_directory", None) self._settings = load_settings( GitHubCopilotSettings, @@ -323,7 +324,7 @@ def __init__( model=model, timeout=timeout, log_level=log_level, - copilot_home=copilot_home, + base_directory=base_directory, env_file_path=env_file_path, env_file_encoding=env_file_encoding, ) @@ -362,14 +363,16 @@ async def start(self) -> None: if self._client is None: cli_path = self._settings.get("cli_path") or None log_level = self._settings.get("log_level") or None - copilot_home = self._settings.get("copilot_home") or None + base_directory = self._settings.get("base_directory") or None - subprocess_kwargs: dict[str, Any] = {"cli_path": cli_path} + client_kwargs: dict[str, Any] = {} + if cli_path: + client_kwargs["connection"] = RuntimeConnection.for_stdio(path=cli_path) if log_level: - subprocess_kwargs["log_level"] = log_level - if copilot_home: - subprocess_kwargs["copilot_home"] = copilot_home - self._client = CopilotClient(SubprocessConfig(**subprocess_kwargs)) + client_kwargs["log_level"] = log_level + if base_directory: + client_kwargs["base_directory"] = base_directory + self._client = CopilotClient(**client_kwargs) try: await self._client.start() diff --git a/python/packages/github_copilot/pyproject.toml b/python/packages/github_copilot/pyproject.toml index 875e9998040..2256c4255f5 100644 --- a/python/packages/github_copilot/pyproject.toml +++ b/python/packages/github_copilot/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ ] dependencies = [ "agent-framework-core>=1.6.0,<2", - "github-copilot-sdk>=1.0.0b2,<=1.0.0b2; python_version >= '3.11'", + "github-copilot-sdk>=1.0.0,<2; 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 a0f0caef725..cfef243b984 100644 --- a/python/packages/github_copilot/tests/test_github_copilot_agent.py +++ b/python/packages/github_copilot/tests/test_github_copilot_agent.py @@ -22,7 +22,7 @@ Message, ) from agent_framework.exceptions import AgentException -from copilot.generated.session_events import ( +from copilot.session_events import ( Data, SessionEvent, SessionEventType, @@ -308,27 +308,27 @@ async def test_start_creates_client_with_options(self) -> None: ) await agent.start() - call_args = MockClient.call_args[0][0] - assert call_args.cli_path == "/custom/path" - assert call_args.log_level == "debug" + kwargs = MockClient.call_args.kwargs + assert kwargs["connection"].path == "/custom/path" + assert kwargs["log_level"] == "debug" - async def test_start_passes_copilot_home_to_subprocess_config(self) -> None: - """Test that copilot_home is passed through to SubprocessConfig.""" + async def test_start_passes_base_directory_to_client(self) -> None: + """Test that base_directory is passed through to CopilotClient.""" with patch("agent_framework_github_copilot._agent.CopilotClient") as MockClient: mock_client = MagicMock() mock_client.start = AsyncMock() MockClient.return_value = mock_client agent: GitHubCopilotAgent[GitHubCopilotOptions] = GitHubCopilotAgent( - default_options={"copilot_home": "/custom/copilot/home"} + default_options={"base_directory": "/custom/copilot/home"} ) await agent.start() - call_args = MockClient.call_args[0][0] - assert call_args.copilot_home == "/custom/copilot/home" + kwargs = MockClient.call_args.kwargs + assert kwargs["base_directory"] == "/custom/copilot/home" - async def test_start_copilot_home_not_set_when_unspecified(self) -> None: - """Test that copilot_home is not included in SubprocessConfig when not specified.""" + async def test_start_base_directory_not_set_when_unspecified(self) -> None: + """Test that base_directory is not included in client kwargs when not specified.""" with patch("agent_framework_github_copilot._agent.CopilotClient") as MockClient: mock_client = MagicMock() mock_client.start = AsyncMock() @@ -337,14 +337,14 @@ async def test_start_copilot_home_not_set_when_unspecified(self) -> None: agent = GitHubCopilotAgent() await agent.start() - call_args = MockClient.call_args[0][0] - assert call_args.copilot_home is None + kwargs = MockClient.call_args.kwargs + assert "base_directory" not in kwargs - async def test_start_copilot_home_from_env_variable(self) -> None: - """Test that copilot_home can be set via GITHUB_COPILOT_COPILOT_HOME env variable.""" + async def test_start_base_directory_from_env_variable(self) -> None: + """Test that base_directory can be set via GITHUB_COPILOT_BASE_DIRECTORY env variable.""" with ( patch("agent_framework_github_copilot._agent.CopilotClient") as MockClient, - patch.dict("os.environ", {"GITHUB_COPILOT_COPILOT_HOME": "/env/copilot/home"}), + patch.dict("os.environ", {"GITHUB_COPILOT_BASE_DIRECTORY": "/env/copilot/home"}), ): mock_client = MagicMock() mock_client.start = AsyncMock() @@ -353,8 +353,8 @@ async def test_start_copilot_home_from_env_variable(self) -> None: agent = GitHubCopilotAgent() await agent.start() - call_args = MockClient.call_args[0][0] - assert call_args.copilot_home == "/env/copilot/home" + kwargs = MockClient.call_args.kwargs + assert kwargs["base_directory"] == "/env/copilot/home" class TestGitHubCopilotAgentRun: @@ -1053,11 +1053,11 @@ async def test_resume_session_includes_tools_and_permissions( mock_session: MagicMock, ) -> None: """Test that resumed session config includes tools and permission handler.""" - from copilot.generated.session_events import PermissionRequest - from copilot.session import PermissionRequestResult + from copilot.session import PermissionDecisionApproveOnce, PermissionRequestResult + from copilot.session_events import PermissionRequest def my_handler(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: - return PermissionRequestResult(kind="approved") + return PermissionDecisionApproveOnce() def my_tool(arg: str) -> str: """A test tool.""" @@ -1876,13 +1876,14 @@ def test_no_permission_handler_when_not_provided(self) -> None: def test_permission_handler_set_when_provided(self) -> None: """Test that a handler is set when on_permission_request is provided.""" - from copilot.generated.session_events import PermissionRequest - from copilot.session import PermissionRequestResult + from copilot.generated.rpc import PermissionDecisionDeniedInteractivelyByUser + from copilot.session import PermissionDecisionApproveOnce, PermissionRequestResult + from copilot.session_events import PermissionRequest def approve_shell(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: if request.kind == "shell": - return PermissionRequestResult(kind="approved") - return PermissionRequestResult(kind="denied-interactively-by-user") + return PermissionDecisionApproveOnce() + return PermissionDecisionDeniedInteractivelyByUser() agent: GitHubCopilotAgent[GitHubCopilotOptions] = GitHubCopilotAgent( default_options={"on_permission_request": approve_shell} @@ -1895,13 +1896,14 @@ async def test_session_config_includes_permission_handler( mock_session: MagicMock, ) -> None: """Test that session config includes permission handler when provided.""" - from copilot.generated.session_events import PermissionRequest - from copilot.session import PermissionRequestResult + from copilot.generated.rpc import PermissionDecisionDeniedInteractivelyByUser + from copilot.session import PermissionDecisionApproveOnce, PermissionRequestResult + from copilot.session_events import PermissionRequest def approve_shell_read(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: if request.kind in ("shell", "read"): - return PermissionRequestResult(kind="approved") - return PermissionRequestResult(kind="denied-interactively-by-user") + return PermissionDecisionApproveOnce() + return PermissionDecisionDeniedInteractivelyByUser() agent: GitHubCopilotAgent[GitHubCopilotOptions] = GitHubCopilotAgent( client=mock_client, diff --git a/python/samples/02-agents/providers/github_copilot/README.md b/python/samples/02-agents/providers/github_copilot/README.md index c3132ed1e92..d58698e7da4 100644 --- a/python/samples/02-agents/providers/github_copilot/README.md +++ b/python/samples/02-agents/providers/github_copilot/README.md @@ -23,7 +23,7 @@ The following environment variables can be configured: | `GITHUB_COPILOT_MODEL` | Model to use (e.g., "gpt-5", "claude-sonnet-4") | Server default | | `GITHUB_COPILOT_TIMEOUT` | Request timeout in seconds | `60` | | `GITHUB_COPILOT_LOG_LEVEL` | CLI log level | `info` | -| `GITHUB_COPILOT_COPILOT_HOME` | Directory for CLI session state and config | `~/.copilot` | +| `GITHUB_COPILOT_BASE_DIRECTORY` | Directory for CLI session state and config | `~/.copilot` | ## Observability diff --git a/python/samples/02-agents/providers/github_copilot/github_copilot_basic.py b/python/samples/02-agents/providers/github_copilot/github_copilot_basic.py index 93e37d89bb1..3cbfe01795b 100644 --- a/python/samples/02-agents/providers/github_copilot/github_copilot_basic.py +++ b/python/samples/02-agents/providers/github_copilot/github_copilot_basic.py @@ -19,8 +19,7 @@ from agent_framework import tool from agent_framework.github import GitHubCopilotAgent -from copilot.generated.session_events import PermissionRequest -from copilot.session import PermissionRequestResult +from copilot.session import PermissionHandler from dotenv import load_dotenv from pydantic import Field @@ -28,19 +27,6 @@ load_dotenv() -def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: - """Permission handler that prompts the user for approval.""" - print(f"\n[Permission Request: {request.kind}]") - - if request.full_command_text is not None: - print(f" Command: {request.full_command_text}") - - response = input("Approve? (y/n): ").strip().lower() - if response in ("y", "yes"): - return PermissionRequestResult(kind="approved") - return PermissionRequestResult(kind="denied-interactively-by-user") - - # NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; # see samples/02-agents/tools/function_tool_with_approval.py # and samples/02-agents/tools/function_tool_with_approval_and_sessions.py. @@ -60,7 +46,7 @@ async def non_streaming_example() -> None: agent = GitHubCopilotAgent( instructions="You are a helpful weather agent.", tools=[get_weather], - default_options={"on_permission_request": prompt_permission}, + default_options={"on_permission_request": PermissionHandler.approve_all}, ) async with agent: @@ -77,7 +63,7 @@ async def streaming_example() -> None: agent = GitHubCopilotAgent( instructions="You are a helpful weather agent.", tools=[get_weather], - default_options={"on_permission_request": prompt_permission}, + default_options={"on_permission_request": PermissionHandler.approve_all}, ) async with agent: @@ -97,7 +83,7 @@ async def runtime_options_example() -> None: agent = GitHubCopilotAgent( instructions="Always respond in exactly 3 words.", tools=[get_weather], - default_options={"on_permission_request": prompt_permission}, + default_options={"on_permission_request": PermissionHandler.approve_all}, ) async with agent: 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 1a82d9867db..67336259d08 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 @@ -4,8 +4,7 @@ GitHub Copilot Agent with File Operation Permissions This sample demonstrates how to enable file read and write operations with GitHubCopilotAgent. -By providing a permission handler that approves "read" and/or "write" requests, the agent can -read from and write to files on the filesystem. +By providing a permission handler, the agent can read from and write to files on the filesystem. SECURITY NOTE: Only enable file permissions when you trust the agent's actions. - "read" allows the agent to read any accessible file @@ -15,21 +14,18 @@ import asyncio from agent_framework.github import GitHubCopilotAgent -from copilot.generated.session_events import PermissionRequest -from copilot.session import PermissionRequestResult +from copilot.generated.rpc import PermissionDecisionDeniedInteractivelyByUser +from copilot.session import PermissionHandler, PermissionRequestResult +from copilot.session_events import PermissionRequest -def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: +async def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: """Permission handler that prompts the user for approval.""" print(f"\n[Permission Request: {request.kind}]") - - if request.path is not None: - print(f" Path: {request.path}") - - response = input("Approve? (y/n): ").strip().lower() + response = (await asyncio.to_thread(input, "Approve? (y/n): ")).strip().lower() if response in ("y", "yes"): - return PermissionRequestResult(kind="approved") - return PermissionRequestResult(kind="denied-interactively-by-user") + return PermissionHandler.approve_all(request, context) + return PermissionDecisionDeniedInteractivelyByUser() async def main() -> None: 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 17520fdf915..3348dbf1a56 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 @@ -32,6 +32,7 @@ from agent_framework import Content, tool from agent_framework.github import GitHubCopilotAgent +from copilot.session import PermissionHandler from dotenv import load_dotenv load_dotenv() @@ -48,37 +49,42 @@ def get_weather_detail(location: Annotated[str, "The city and state, e.g. San Fr ) -def prompt_for_approval(call: Content) -> bool: - """Synchronous approval prompt. +async def prompt_for_approval(call: Content) -> bool: + """Async approval callback that prompts the user interactively. The callback receives a ``FunctionCallContent`` so the operator can review the tool name and arguments before deciding. Returning ``True`` allows the call; returning ``False`` denies it and a tool-error is returned to the model. + + Uses ``asyncio.to_thread`` so the event loop is not blocked by ``input()``. """ - print(f"\n[Function Approval Request]\n Tool: {call.name}\n Arguments: {call.arguments}") - response = input("Approve this tool call? (y/n): ").strip().lower() + print(f"\n [Function Approval Request]\n Tool: {call.name}\n Arguments: {call.arguments}") + response = (await asyncio.to_thread(input, " Approve this tool call? (y/n): ")).strip().lower() return response in ("y", "yes") -async def prompt_for_approval_async(call: Content) -> bool: - """Async approval prompt. +def auto_approve(call: Content) -> bool: + """Synchronous approval callback that always approves. - Use an async callback when approval requires I/O (e.g. an HTTP call to a - review service or queueing the request to a UI). ``input()`` is wrapped - with ``asyncio.to_thread`` so the event loop is not blocked. + Use a sync callback for simple, non-blocking decisions that don't require + I/O (e.g. checking an allow-list of tool names). """ - print(f"\n[Function Approval Request - async]\n Tool: {call.name}\n Arguments: {call.arguments}") - response = await asyncio.to_thread(input, "Approve this tool call? (y/n): ") - return response.strip().lower() in ("y", "yes") + print(f"\n [Function Approval Request]\n Tool: {call.name}\n Arguments: {call.arguments}") + print(" -> Auto-approved") + return True -async def run_with_sync_callback() -> None: - print("\n=== GitHub Copilot Agent: synchronous approval callback ===") +async def run_with_interactive_callback() -> None: + """Demonstrates an interactive approval prompt before tool execution.""" + print("\n=== GitHub Copilot Agent: interactive approval callback ===") agent = GitHubCopilotAgent( instructions="You are a helpful weather assistant.", tools=[get_weather_detail], - default_options={"on_function_approval": prompt_for_approval}, + default_options={ + "on_function_approval": prompt_for_approval, + "on_permission_request": PermissionHandler.approve_all, + }, ) async with agent: query = "Give me the detailed weather for Seattle." @@ -87,12 +93,16 @@ async def run_with_sync_callback() -> None: print(f"Agent: {result}") -async def run_with_async_callback() -> None: - print("\n=== GitHub Copilot Agent: asynchronous approval callback ===") +async def run_with_auto_approve_callback() -> None: + """Demonstrates a synchronous callback that always approves.""" + print("\n=== GitHub Copilot Agent: synchronous auto-approve callback ===") agent = GitHubCopilotAgent( instructions="You are a helpful weather assistant.", tools=[get_weather_detail], - default_options={"on_function_approval": prompt_for_approval_async}, + default_options={ + "on_function_approval": auto_approve, + "on_permission_request": PermissionHandler.approve_all, + }, ) async with agent: query = "Give me the detailed weather for Tokyo." @@ -112,6 +122,7 @@ async def run_without_callback() -> None: agent = GitHubCopilotAgent( instructions="You are a helpful weather assistant.", tools=[get_weather_detail], + default_options={"on_permission_request": PermissionHandler.approve_all}, ) async with agent: query = "Give me the detailed weather for Paris." @@ -122,8 +133,8 @@ async def run_without_callback() -> None: async def main() -> None: print("=== GitHub Copilot Agent: Function approval enforcement ===") - await run_with_sync_callback() - await run_with_async_callback() + await run_with_interactive_callback() + await run_with_auto_approve_callback() await run_without_callback() diff --git a/python/samples/02-agents/providers/github_copilot/github_copilot_with_instruction_directories.py b/python/samples/02-agents/providers/github_copilot/github_copilot_with_instruction_directories.py index e30f114f7de..4c7ae2c1a42 100644 --- a/python/samples/02-agents/providers/github_copilot/github_copilot_with_instruction_directories.py +++ b/python/samples/02-agents/providers/github_copilot/github_copilot_with_instruction_directories.py @@ -22,24 +22,13 @@ from pathlib import Path from agent_framework.github import GitHubCopilotAgent -from copilot.generated.session_events import PermissionRequest -from copilot.session import PermissionRequestResult +from copilot.session import PermissionHandler from dotenv import load_dotenv # Load environment variables from .env file load_dotenv() -def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: - """Permission handler that prompts the user for approval.""" - print(f"\n[Permission Request: {request.kind}]") - - response = input("Approve? (y/n): ").strip().lower() - if response in ("y", "yes"): - return PermissionRequestResult(kind="approved") - return PermissionRequestResult(kind="denied-interactively-by-user") - - async def default_instructions_example() -> None: """Example of pointing the agent at project-specific instruction directories.""" print("=== Instruction Directories (Default) ===\n") @@ -58,7 +47,7 @@ async def default_instructions_example() -> None: agent = GitHubCopilotAgent( instructions="You are a helpful coding assistant.", default_options={ - "on_permission_request": prompt_permission, + "on_permission_request": PermissionHandler.approve_all, "instruction_directories": instruction_dirs, }, ) @@ -79,7 +68,7 @@ async def runtime_override_example() -> None: agent = GitHubCopilotAgent( instructions="You are a helpful assistant.", default_options={ - "on_permission_request": prompt_permission, + "on_permission_request": PermissionHandler.approve_all, "instruction_directories": ["/team/shared/instructions"], }, ) diff --git a/python/samples/02-agents/providers/github_copilot/github_copilot_with_mcp.py b/python/samples/02-agents/providers/github_copilot/github_copilot_with_mcp.py index 71bd67efb4d..60e4704c078 100644 --- a/python/samples/02-agents/providers/github_copilot/github_copilot_with_mcp.py +++ b/python/samples/02-agents/providers/github_copilot/github_copilot_with_mcp.py @@ -15,24 +15,13 @@ import asyncio from agent_framework.github import GitHubCopilotAgent -from copilot.generated.session_events import PermissionRequest -from copilot.session import MCPServerConfig, PermissionRequestResult +from copilot.session import MCPServerConfig, PermissionHandler from dotenv import load_dotenv # Load environment variables from .env file load_dotenv() -def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: - """Permission handler that prompts the user for approval.""" - print(f"\n[Permission Request: {request.kind}]") - - response = input("Approve? (y/n): ").strip().lower() - if response in ("y", "yes"): - return PermissionRequestResult(kind="approved") - return PermissionRequestResult(kind="denied-interactively-by-user") - - async def main() -> None: print("=== GitHub Copilot Agent with MCP Servers ===\n") @@ -56,7 +45,7 @@ async def main() -> None: agent = GitHubCopilotAgent( instructions="You are a helpful assistant with access to the local filesystem and Microsoft Learn.", default_options={ - "on_permission_request": prompt_permission, + "on_permission_request": PermissionHandler.approve_all, "mcp_servers": mcp_servers, }, ) 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 916061f9399..5da43b32744 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 @@ -3,9 +3,8 @@ """ GitHub Copilot Agent with Multiple Permissions -This sample demonstrates how to enable multiple permission types with GitHubCopilotAgent. -By combining different permission kinds in the handler, the agent can perform complex tasks -that require multiple capabilities. +This sample demonstrates how multiple permission types are requested when GitHubCopilotAgent +performs complex tasks that require different capabilities. Available permission kinds: - "shell": Execute shell commands @@ -21,23 +20,14 @@ import asyncio from agent_framework.github import GitHubCopilotAgent -from copilot.generated.session_events import PermissionRequest -from copilot.session import PermissionRequestResult +from copilot.session import PermissionHandler, PermissionRequestResult +from copilot.session_events import PermissionRequest -def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: - """Permission handler that prompts the user for approval.""" - print(f"\n[Permission Request: {request.kind}]") - - if request.full_command_text is not None: - print(f" Command: {request.full_command_text}") - if request.path is not None: - print(f" Path: {request.path}") - - response = input("Approve? (y/n): ").strip().lower() - if response in ("y", "yes"): - return PermissionRequestResult(kind="approved") - return PermissionRequestResult(kind="denied-interactively-by-user") +def approve_and_log(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: + """Permission handler that auto-approves and logs each permission kind.""" + print(f" [Permission: {request.kind}]", flush=True) + return PermissionHandler.approve_all(request, context) async def main() -> None: @@ -45,14 +35,14 @@ async def main() -> None: agent = GitHubCopilotAgent( instructions="You are a helpful development assistant that can read, write files and run commands.", - default_options={"on_permission_request": prompt_permission}, + default_options={"on_permission_request": approve_and_log}, ) async with agent: query = "List the first 3 Python files, then read the first one and create a summary in summary.txt" - print(f"User: {query}") + print(f"User: {query}\n") result = await agent.run(query) - print(f"Agent: {result}\n") + print(f"\nAgent: {result}\n") if __name__ == "__main__": diff --git a/python/samples/02-agents/providers/github_copilot/github_copilot_with_session.py b/python/samples/02-agents/providers/github_copilot/github_copilot_with_session.py index 801edf52f31..70a0f3c8260 100644 --- a/python/samples/02-agents/providers/github_copilot/github_copilot_with_session.py +++ b/python/samples/02-agents/providers/github_copilot/github_copilot_with_session.py @@ -14,24 +14,10 @@ from agent_framework import tool from agent_framework.github import GitHubCopilotAgent -from copilot.generated.session_events import PermissionRequest -from copilot.session import PermissionRequestResult +from copilot.session import PermissionHandler from pydantic import Field -def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: - """Permission handler that prompts the user for approval.""" - print(f"\n[Permission Request: {request.kind}]") - - if request.full_command_text is not None: - print(f" Command: {request.full_command_text}") - - response = input("Approve? (y/n): ").strip().lower() - if response in ("y", "yes"): - return PermissionRequestResult(kind="approved") - return PermissionRequestResult(kind="denied-interactively-by-user") - - # NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; # see samples/02-agents/tools/function_tool_with_approval.py # and samples/02-agents/tools/function_tool_with_approval_and_sessions.py. @@ -51,7 +37,7 @@ async def example_with_automatic_session_creation() -> None: agent = GitHubCopilotAgent( instructions="You are a helpful weather agent.", tools=[get_weather], - default_options={"on_permission_request": prompt_permission}, + default_options={"on_permission_request": PermissionHandler.approve_all}, ) async with agent: @@ -76,7 +62,7 @@ async def example_with_session_persistence() -> None: agent = GitHubCopilotAgent( instructions="You are a helpful weather agent.", tools=[get_weather], - default_options={"on_permission_request": prompt_permission}, + default_options={"on_permission_request": PermissionHandler.approve_all}, ) async with agent: @@ -113,7 +99,7 @@ async def example_with_existing_session_id() -> None: agent1 = GitHubCopilotAgent( instructions="You are a helpful weather agent.", tools=[get_weather], - default_options={"on_permission_request": prompt_permission}, + default_options={"on_permission_request": PermissionHandler.approve_all}, ) async with agent1: @@ -135,7 +121,7 @@ async def example_with_existing_session_id() -> None: agent2 = GitHubCopilotAgent( instructions="You are a helpful weather agent.", tools=[get_weather], - default_options={"on_permission_request": prompt_permission}, + default_options={"on_permission_request": PermissionHandler.approve_all}, ) async with agent2: 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 729aad6863f..c93134f121d 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 @@ -14,21 +14,16 @@ import asyncio from agent_framework.github import GitHubCopilotAgent -from copilot.generated.session_events import PermissionRequest -from copilot.session import PermissionRequestResult +from copilot.session import PermissionHandler, PermissionRequestResult +from copilot.session_events import PermissionRequest -def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: - """Permission handler that prompts the user for approval.""" - print(f"\n[Permission Request: {request.kind}]") - +def approve_and_log(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: + """Permission handler that auto-approves and logs shell commands.""" + print(f"\n [Permission: {request.kind}]", flush=True) if request.full_command_text is not None: - print(f" Command: {request.full_command_text}") - - response = input("Approve? (y/n): ").strip().lower() - if response in ("y", "yes"): - return PermissionRequestResult(kind="approved") - return PermissionRequestResult(kind="denied-interactively-by-user") + print(f" Command: {request.full_command_text}", flush=True) + return PermissionHandler.approve_all(request, context) async def main() -> None: @@ -36,14 +31,14 @@ async def main() -> None: agent = GitHubCopilotAgent( instructions="You are a helpful assistant that can execute shell commands.", - default_options={"on_permission_request": prompt_permission}, + default_options={"on_permission_request": approve_and_log}, ) async with agent: query = "List the first 3 Python files in the current directory" print(f"User: {query}") result = await agent.run(query) - print(f"Agent: {result}\n") + print(f"\nAgent: {result}\n") if __name__ == "__main__": 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 2f14648bae8..89b547c9bf4 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 @@ -14,21 +14,16 @@ import asyncio from agent_framework.github import GitHubCopilotAgent -from copilot.generated.session_events import PermissionRequest -from copilot.session import PermissionRequestResult +from copilot.session import PermissionHandler, PermissionRequestResult +from copilot.session_events import PermissionRequest -def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: - """Permission handler that prompts the user for approval.""" - print(f"\n[Permission Request: {request.kind}]") - +def approve_and_log(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: + """Permission handler that auto-approves and logs the requested URL.""" + print(f"\n [Permission: {request.kind}]", flush=True) if request.url is not None: - print(f" URL: {request.url}") - - response = input("Approve? (y/n): ").strip().lower() - if response in ("y", "yes"): - return PermissionRequestResult(kind="approved") - return PermissionRequestResult(kind="denied-interactively-by-user") + print(f" URL: {request.url}", flush=True) + return PermissionHandler.approve_all(request, context) async def main() -> None: @@ -36,14 +31,14 @@ async def main() -> None: agent = GitHubCopilotAgent( instructions="You are a helpful assistant that can fetch and summarize web content.", - default_options={"on_permission_request": prompt_permission}, + default_options={"on_permission_request": approve_and_log}, ) async with agent: query = "Fetch https://learn.microsoft.com/agent-framework/tutorials/quick-start and summarize its contents" print(f"User: {query}") result = await agent.run(query) - print(f"Agent: {result}\n") + print(f"\nAgent: {result}\n") if __name__ == "__main__": diff --git a/python/uv.lock b/python/uv.lock index 5e4ae35369f..e145d938e1c 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -562,7 +562,7 @@ requires-dist = [ { name = "agent-framework-core", editable = "packages/core" }, { name = "azure-ai-agentserver-core", specifier = ">=2.0.0b3,<3" }, { name = "azure-ai-agentserver-invocations", specifier = ">=1.0.0b3,<2" }, - { name = "azure-ai-agentserver-responses", specifier = ">=1.0.0b5,<2" }, + { name = "azure-ai-agentserver-responses", specifier = ">=1.0.0b7,<2" }, ] [[package]] @@ -609,7 +609,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.0b2,<=1.0.0b2" }, + { name = "github-copilot-sdk", marker = "python_full_version >= '3.11'", specifier = ">=1.0.0,<2" }, ] [[package]] @@ -1171,19 +1171,18 @@ wheels = [ [[package]] name = "azure-ai-agentserver-core" -version = "2.0.0b3" +version = "2.0.0b5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "azure-monitor-opentelemetry-exporter", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "hypercorn", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "microsoft-opentelemetry", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "opentelemetry-exporter-otlp-proto-grpc", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "opentelemetry-sdk", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "starlette", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/84/29/1a9606d5252b02d77070a1b633dd0c26fe65a0f4a0fb0cfdaa751e2ed458/azure_ai_agentserver_core-2.0.0b3.tar.gz", hash = "sha256:e295b19a65d53c513929f52f0862bbb815cc9e9fc29d2a2825452f3136260123", size = 42573, upload-time = "2026-04-23T04:13:16.717Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/06/7c88b6506d26ee625a967cef762e6a155ed7ab8812f3f1e45ec1a950b8ae/azure_ai_agentserver_core-2.0.0b5.tar.gz", hash = "sha256:f03dc737351e5d847e9fc18c5b78b261436de368f1317a0c29957cc2179c37d1", size = 46273, upload-time = "2026-05-25T12:48:01.739Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/9b/1fc87c05b55821f33c46c5e8a3b97a573aa2fc4bff387e75cca1a87800b4/azure_ai_agentserver_core-2.0.0b3-py3-none-any.whl", hash = "sha256:5ef921eb9fd9c0f15682fe930320fae50dccfa915d7518f9a16d99014bbcb3cb", size = 29127, upload-time = "2026-04-23T04:13:17.976Z" }, + { url = "https://files.pythonhosted.org/packages/68/80/a43a269601512793b220c36dc0864b44d806b969dbfe14f1ecc3b5f5202b/azure_ai_agentserver_core-2.0.0b5-py3-none-any.whl", hash = "sha256:0d00c298892e2ff466b32235d5d9c55b57054f0e8fcedb0726eacd7684e1aa89", size = 31521, upload-time = "2026-05-25T12:48:03.072Z" }, ] [[package]] @@ -1200,7 +1199,7 @@ wheels = [ [[package]] name = "azure-ai-agentserver-responses" -version = "1.0.0b5" +version = "1.0.0b7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -1208,9 +1207,9 @@ dependencies = [ { name = "azure-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "isodate", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/27/3ecb7fe704ff8764199bfbe4cc1e584a520a9affe042470d9d50b6e1e73a/azure_ai_agentserver_responses-1.0.0b5.tar.gz", hash = "sha256:0b627b810359c792ea7b6fa6782abaf6df32d9bc9e5a569ad722afcffd0ce8d9", size = 410908, upload-time = "2026-04-23T04:31:15.414Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/53/febb6f3453f5dc1e0b6dc47d4e5198b64605d1f83c847255946f74bc300e/azure_ai_agentserver_responses-1.0.0b7.tar.gz", hash = "sha256:2f67cdfc0219cb0ab86800dadb1cfdb40ab4aa0413dae7ffa5ea4ea84eec3eb0", size = 419032, upload-time = "2026-05-25T12:48:38.81Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/91/1e5c0d7ce95ca8b022e69e4ca6b23e413fc2d57f0191429c4633e02213d2/azure_ai_agentserver_responses-1.0.0b5-py3-none-any.whl", hash = "sha256:4c2a6ab56e71eeb330aa52b7cb2cc71b8ec6b5bbe0e7dc84310f2c7fbda393a3", size = 268362, upload-time = "2026-04-23T04:31:17.014Z" }, + { url = "https://files.pythonhosted.org/packages/b3/94/48825357e009f7db3b6b5d0a9344a7ab3304e32f06f50328b2393e3b06cb/azure_ai_agentserver_responses-1.0.0b7-py3-none-any.whl", hash = "sha256:efb5271f24a297bacde9769359308e54e870f66ad4d3b4826ae97a77e40e94d4", size = 268063, upload-time = "2026-05-25T12:48:40.817Z" }, ] [[package]] @@ -2609,19 +2608,19 @@ wheels = [ [[package]] name = "github-copilot-sdk" -version = "1.0.0b2" +version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic", marker = "(python_full_version >= '3.11' and sys_platform == 'darwin') or (python_full_version >= '3.11' and sys_platform == 'linux') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, { name = "python-dateutil", marker = "(python_full_version >= '3.11' and sys_platform == 'darwin') or (python_full_version >= '3.11' and sys_platform == 'linux') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/82/fe/2cb98d4b9f57f8062ea72775bde72aed1958305016753f7296398e0ceb45/github_copilot_sdk-1.0.0b2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:1b5941d8b6e3d94d42a5bec6607a26f562e6535d5c981089d23d3d224b94601c", size = 67061619, upload-time = "2026-05-06T20:02:08.636Z" }, - { url = "https://files.pythonhosted.org/packages/57/45/76567821b2d36f81e6bca78c98d265e2762733f765fa51d69602b7f81867/github_copilot_sdk-1.0.0b2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b8f6a087a0cf02bb0d33976e8f8c009578d84d701a0b28d52051304791ac70", size = 63790955, upload-time = "2026-05-06T20:02:12.354Z" }, - { url = "https://files.pythonhosted.org/packages/15/67/684b0da0b1207a2bdf025c22ee075d34a1736d61a4973651035d4fd4d8dc/github_copilot_sdk-1.0.0b2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:f403638c11b82bddb81c94675fc4e8014a1bb2e86a679a39fa167dcc3ad5416a", size = 69538664, upload-time = "2026-05-06T20:02:16.363Z" }, - { url = "https://files.pythonhosted.org/packages/57/1d/80d88ecf83683535d1a16d4817f1683db3b125f52a924ebdfe9764f5e4c3/github_copilot_sdk-1.0.0b2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:433d16bb31171fee8d3a5b70259c527f63b297e83a8f8761ae1f16f14d641f32", size = 68163648, upload-time = "2026-05-06T20:02:21.139Z" }, - { url = "https://files.pythonhosted.org/packages/32/d3/b72aa2fbb3194b50b53e8cb1484f5606a1f8eedcdb0bfb5747da52079553/github_copilot_sdk-1.0.0b2-py3-none-win_amd64.whl", hash = "sha256:a6e9782dae4c3c2ab3527b45bb5de0f61998104c10e9ff64698280eaf37ab5dd", size = 62649144, upload-time = "2026-05-06T20:02:24.953Z" }, - { url = "https://files.pythonhosted.org/packages/b6/e2/be95b8ea0ac11d1ca474e28a59284f4e395c2710734eadfb657f5de8ace2/github_copilot_sdk-1.0.0b2-py3-none-win_arm64.whl", hash = "sha256:2e97d0ce4bad67dc5929091cb429e7bbae7d4643e4908a6af256a41439000740", size = 60374365, upload-time = "2026-05-06T20:02:29.02Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d2/e74fdf476d0dde5c3802b3ba360f1b1e250e55d6d39c03f578c28ac9864e/github_copilot_sdk-1.0.0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:3cae245fb825e26a74395b74f10d9fd90bc464aa77005848ae0809c9a46c96df", size = 94986104, upload-time = "2026-06-02T14:59:55.022Z" }, + { url = "https://files.pythonhosted.org/packages/b6/81/e4d9dd01b0a563e488427aa879166287c88de3fccf7b8a95e22a6c652fc3/github_copilot_sdk-1.0.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b344a00a877c86ef717244e42bd01acb3694b7377644661c82fc278ccc990e37", size = 91435649, upload-time = "2026-06-02T15:00:02.567Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ec/e94b8f5a299850e600ffe1fe14bd21b48e01172b9e8b490a0ebd0d0c8d27/github_copilot_sdk-1.0.0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:dd3a6b7637a3b12476854aeb599c6bed030f6a166fbd942d872c9a11a695517c", size = 97301959, upload-time = "2026-06-02T15:00:11.019Z" }, + { url = "https://files.pythonhosted.org/packages/4b/bf/dfba743a11d9745b0664ec5e1ae6e05055a5cbef0ccc6d593222319184eb/github_copilot_sdk-1.0.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:bbd2c64fe37016c74620a02d778eaacbd526b4c3b668a3cdff019f831c752eee", size = 96071193, upload-time = "2026-06-02T15:00:22.634Z" }, + { url = "https://files.pythonhosted.org/packages/0e/9b/d953dcbb898f4d44efc0cb592e9a703ad43a4b673aafb5bbd763962ab2fd/github_copilot_sdk-1.0.0-py3-none-win_amd64.whl", hash = "sha256:2d46fff634eece978532b1329c0d9e1d784b08ad521e71e6af06c5c28ae2e7c5", size = 90374124, upload-time = "2026-06-02T15:00:31.376Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f7/0f9943b1439e3dcc52854140676b65d8f63405c471a77c58291a8f4bfb52/github_copilot_sdk-1.0.0-py3-none-win_arm64.whl", hash = "sha256:ebfb80395caa834df8ab16ab4aab3e5d8db883ed3b024f723c394b1514e47221", size = 87874846, upload-time = "2026-06-02T15:00:38.737Z" }, ] [[package]] @@ -2709,6 +2708,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/38/3f/9859f655d11901e7b2996c6e3d33e0caa9a1d4572c3bc61ed0faa64b2f4c/greenlet-3.3.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9bc885b89709d901859cf95179ec9f6bb67a3d2bb1f0e88456461bd4b7f8fd0d", size = 277747, upload-time = "2026-02-20T20:16:21.325Z" }, { url = "https://files.pythonhosted.org/packages/fb/07/cb284a8b5c6498dbd7cba35d31380bb123d7dceaa7907f606c8ff5993cbf/greenlet-3.3.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b568183cf65b94919be4438dc28416b234b678c608cafac8874dfeeb2a9bbe13", size = 579202, upload-time = "2026-02-20T20:47:28.955Z" }, { url = "https://files.pythonhosted.org/packages/ed/45/67922992b3a152f726163b19f890a85129a992f39607a2a53155de3448b8/greenlet-3.3.2-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:527fec58dc9f90efd594b9b700662ed3fb2493c2122067ac9c740d98080a620e", size = 590620, upload-time = "2026-02-20T20:55:55.581Z" }, + { url = "https://files.pythonhosted.org/packages/03/5f/6e2a7d80c353587751ef3d44bb947f0565ec008a2e0927821c007e96d3a7/greenlet-3.3.2-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:508c7f01f1791fbc8e011bd508f6794cb95397fdb198a46cb6635eb5b78d85a7", size = 602132, upload-time = "2026-02-20T21:02:43.261Z" }, { url = "https://files.pythonhosted.org/packages/ad/55/9f1ebb5a825215fadcc0f7d5073f6e79e3007e3282b14b22d6aba7ca6cb8/greenlet-3.3.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad0c8917dd42a819fe77e6bdfcb84e3379c0de956469301d9fd36427a1ca501f", size = 591729, upload-time = "2026-02-20T20:20:58.395Z" }, { url = "https://files.pythonhosted.org/packages/24/b4/21f5455773d37f94b866eb3cf5caed88d6cea6dd2c6e1f9c34f463cba3ec/greenlet-3.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:97245cc10e5515dbc8c3104b2928f7f02b6813002770cfaffaf9a6e0fc2b94ef", size = 1551946, upload-time = "2026-02-20T20:49:31.102Z" }, { url = "https://files.pythonhosted.org/packages/00/68/91f061a926abead128fe1a87f0b453ccf07368666bd59ffa46016627a930/greenlet-3.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8c1fdd7d1b309ff0da81d60a9688a8bd044ac4e18b250320a96fc68d31c209ca", size = 1618494, upload-time = "2026-02-20T20:21:06.541Z" }, @@ -2716,6 +2716,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f3/47/16400cb42d18d7a6bb46f0626852c1718612e35dcb0dffa16bbaffdf5dd2/greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86", size = 278890, upload-time = "2026-02-20T20:19:39.263Z" }, { url = "https://files.pythonhosted.org/packages/a3/90/42762b77a5b6aa96cd8c0e80612663d39211e8ae8a6cd47c7f1249a66262/greenlet-3.3.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ebd458fa8285960f382841da585e02201b53a5ec2bac6b156fc623b5ce4499f", size = 581120, upload-time = "2026-02-20T20:47:30.161Z" }, { url = "https://files.pythonhosted.org/packages/bf/6f/f3d64f4fa0a9c7b5c5b3c810ff1df614540d5aa7d519261b53fba55d4df9/greenlet-3.3.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a443358b33c4ec7b05b79a7c8b466f5d275025e750298be7340f8fc63dff2a55", size = 594363, upload-time = "2026-02-20T20:55:56.965Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8b/1430a04657735a3f23116c2e0d5eb10220928846e4537a938a41b350bed6/greenlet-3.3.2-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4375a58e49522698d3e70cc0b801c19433021b5c37686f7ce9c65b0d5c8677d2", size = 605046, upload-time = "2026-02-20T21:02:45.234Z" }, { url = "https://files.pythonhosted.org/packages/72/83/3e06a52aca8128bdd4dcd67e932b809e76a96ab8c232a8b025b2850264c5/greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358", size = 594156, upload-time = "2026-02-20T20:20:59.955Z" }, { url = "https://files.pythonhosted.org/packages/70/79/0de5e62b873e08fe3cef7dbe84e5c4bc0e8ed0c7ff131bccb8405cd107c8/greenlet-3.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:442b6057453c8cb29b4fb36a2ac689382fc71112273726e2423f7f17dc73bf99", size = 1554649, upload-time = "2026-02-20T20:49:32.293Z" }, { url = "https://files.pythonhosted.org/packages/5a/00/32d30dee8389dc36d42170a9c66217757289e2afb0de59a3565260f38373/greenlet-3.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45abe8eb6339518180d5a7fa47fa01945414d7cca5ecb745346fc6a87d2750be", size = 1619472, upload-time = "2026-02-20T20:21:07.966Z" }, @@ -2724,6 +2725,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358, upload-time = "2026-02-20T20:17:43.971Z" }, { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217, upload-time = "2026-02-20T20:47:31.462Z" }, { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792, upload-time = "2026-02-20T20:55:58.423Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c5/cc09412a29e43406eba18d61c70baa936e299bc27e074e2be3806ed29098/greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb", size = 626250, upload-time = "2026-02-20T21:02:46.596Z" }, { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875, upload-time = "2026-02-20T20:21:01.102Z" }, { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467, upload-time = "2026-02-20T20:49:33.495Z" }, { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001, upload-time = "2026-02-20T20:21:09.154Z" }, @@ -2732,6 +2734,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120, upload-time = "2026-02-20T20:19:01.9Z" }, { url = "https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238, upload-time = "2026-02-20T20:47:32.873Z" }, { url = "https://files.pythonhosted.org/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219, upload-time = "2026-02-20T20:55:59.817Z" }, + { url = "https://files.pythonhosted.org/packages/94/2b/4d012a69759ac9d77210b8bfb128bc621125f5b20fc398bce3940d036b1c/greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd", size = 628268, upload-time = "2026-02-20T21:02:48.024Z" }, { url = "https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774, upload-time = "2026-02-20T20:21:02.454Z" }, { url = "https://files.pythonhosted.org/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277, upload-time = "2026-02-20T20:49:34.795Z" }, { url = "https://files.pythonhosted.org/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455, upload-time = "2026-02-20T20:21:10.261Z" }, @@ -2740,6 +2743,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/ae/8bffcbd373b57a5992cd077cbe8858fff39110480a9d50697091faea6f39/greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab", size = 279650, upload-time = "2026-02-20T20:18:00.783Z" }, { url = "https://files.pythonhosted.org/packages/d1/c0/45f93f348fa49abf32ac8439938726c480bd96b2a3c6f4d949ec0124b69f/greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082", size = 650295, upload-time = "2026-02-20T20:47:34.036Z" }, { url = "https://files.pythonhosted.org/packages/b3/de/dd7589b3f2b8372069ab3e4763ea5329940fc7ad9dcd3e272a37516d7c9b/greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9", size = 662163, upload-time = "2026-02-20T20:56:01.295Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ac/85804f74f1ccea31ba518dcc8ee6f14c79f73fe36fa1beba38930806df09/greenlet-3.3.2-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3cb43ce200f59483eb82949bf1835a99cf43d7571e900d7c8d5c62cdf25d2f9", size = 675371, upload-time = "2026-02-20T21:02:49.664Z" }, { url = "https://files.pythonhosted.org/packages/d2/d8/09bfa816572a4d83bccd6750df1926f79158b1c36c5f73786e26dbe4ee38/greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506", size = 664160, upload-time = "2026-02-20T20:21:04.015Z" }, { url = "https://files.pythonhosted.org/packages/48/cf/56832f0c8255d27f6c35d41b5ec91168d74ec721d85f01a12131eec6b93c/greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce", size = 1619181, upload-time = "2026-02-20T20:49:36.052Z" }, { url = "https://files.pythonhosted.org/packages/0a/23/b90b60a4aabb4cec0796e55f25ffbfb579a907c3898cd2905c8918acaa16/greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5", size = 1687713, upload-time = "2026-02-20T20:21:11.684Z" }, @@ -2748,6 +2752,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/98/6d/8f2ef704e614bcf58ed43cfb8d87afa1c285e98194ab2cfad351bf04f81e/greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54", size = 286617, upload-time = "2026-02-20T20:19:29.856Z" }, { url = "https://files.pythonhosted.org/packages/5e/0d/93894161d307c6ea237a43988f27eba0947b360b99ac5239ad3fe09f0b47/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4", size = 655189, upload-time = "2026-02-20T20:47:35.742Z" }, { url = "https://files.pythonhosted.org/packages/f5/2c/d2d506ebd8abcb57386ec4f7ba20f4030cbe56eae541bc6fd6ef399c0b41/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff", size = 658225, upload-time = "2026-02-20T20:56:02.527Z" }, + { url = "https://files.pythonhosted.org/packages/d1/67/8197b7e7e602150938049d8e7f30de1660cfb87e4c8ee349b42b67bdb2e1/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:59b3e2c40f6706b05a9cd299c836c6aa2378cabe25d021acd80f13abf81181cf", size = 666581, upload-time = "2026-02-20T21:02:51.526Z" }, { url = "https://files.pythonhosted.org/packages/8e/30/3a09155fbf728673a1dea713572d2d31159f824a37c22da82127056c44e4/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4", size = 657907, upload-time = "2026-02-20T20:21:05.259Z" }, { url = "https://files.pythonhosted.org/packages/f3/fd/d05a4b7acd0154ed758797f0a43b4c0962a843bedfe980115e842c5b2d08/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727", size = 1618857, upload-time = "2026-02-20T20:49:37.309Z" }, { url = "https://files.pythonhosted.org/packages/6f/e1/50ee92a5db521de8f35075b5eff060dd43d39ebd46c2181a2042f7070385/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e", size = 1680010, upload-time = "2026-02-20T20:21:13.427Z" }, @@ -3887,6 +3892,41 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f0/1b/543ddaa2daf8593911a02a07a6a78366d4a6a0053ec86a557c19fa97b60e/microsoft_agents_hosting_core-0.3.1-py3-none-any.whl", hash = "sha256:a4b41556b15321b74f539c5a0a89f70955459b7ec57e9e4b24e61bba27f1cbbc", size = 94573, upload-time = "2025-09-09T23:19:53.855Z" }, ] +[[package]] +name = "microsoft-opentelemetry" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "azure-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "azure-core-tracing-opentelemetry", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "azure-monitor-opentelemetry-exporter", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-exporter-otlp-proto-http", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-django", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-fastapi", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-flask", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-httpx", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-logging", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-openai-agents-v2", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-openai-v2", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-psycopg2", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-requests", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-urllib", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation-urllib3", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-resource-detector-azure", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-sdk", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-util-genai", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "pyjwt", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "requests", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "wrapt", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/cf/74885d07d38e225b84b63a8a2720de846e518fe4c7e89457f4c150a9c7d5/microsoft_opentelemetry-1.3.2.tar.gz", hash = "sha256:d36f31731740170624b53f370358a9700f503bb4f9bd25c7f81c0c88c66f511c", size = 178031, upload-time = "2026-05-29T22:05:53.442Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/8d/6960be61c8fe236fef730b0cae1d97a1898f62355b2d6679ef46abe1e4be/microsoft_opentelemetry-1.3.2-py3-none-any.whl", hash = "sha256:65292474ce7efee115f671457188e92edc4a8d432fad163e49e504155be66ae5", size = 198419, upload-time = "2026-05-29T22:05:54.849Z" }, +] + [[package]] name = "mistralai" version = "2.4.2" @@ -4633,6 +4673,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3e/41/619f3530324a58491f2d20f216a10dd7393629b29db4610dda642a27f4ed/opentelemetry_instrumentation_flask-0.61b0-py3-none-any.whl", hash = "sha256:e8ce474d7ce543bfbbb3e93f8a6f8263348af9d7b45502f387420cf3afa71253", size = 15996, upload-time = "2026-03-04T14:19:31.304Z" }, ] +[[package]] +name = "opentelemetry-instrumentation-httpx" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-semantic-conventions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-util-http", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "wrapt", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/2a/e2becd55e33c29d1d9ef76e2579040ed1951cb33bacba259f6aff2fdd2a6/opentelemetry_instrumentation_httpx-0.61b0.tar.gz", hash = "sha256:6569ec097946c5551c2a4252f74c98666addd1bf047c1dde6b4ef426719ff8dd", size = 24104, upload-time = "2026-03-04T14:20:34.752Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/88/dde310dce56e2d85cf1a09507f5888544955309edc4b8d22971d6d3d1417/opentelemetry_instrumentation_httpx-0.61b0-py3-none-any.whl", hash = "sha256:dee05c93a6593a5dc3ae5d9d5c01df8b4e2c5d02e49275e5558534ee46343d5e", size = 17198, upload-time = "2026-03-04T14:19:33.585Z" }, +] + [[package]] name = "opentelemetry-instrumentation-logging" version = "0.61b0" @@ -4646,6 +4702,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/0e/2137db5239cc5e564495549a4d11488a7af9b48fc76520a0eea20e69ddae/opentelemetry_instrumentation_logging-0.61b0-py3-none-any.whl", hash = "sha256:6d87e5ded6a0128d775d41511f8380910a1b610671081d16efb05ac3711c0074", size = 17076, upload-time = "2026-03-04T14:19:36.765Z" }, ] +[[package]] +name = "opentelemetry-instrumentation-openai-agents-v2" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-semantic-conventions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-util-genai", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/15/b6a303454d2800d772cdebc490c1d598d06d0e541619db80195eb9ea85c6/opentelemetry_instrumentation_openai_agents_v2-0.1.0.tar.gz", hash = "sha256:1033f4b261ce07f65d197ac0e9c499302c805eae987a6cc4e7f99bb279363477", size = 22423, upload-time = "2025-10-15T19:04:59.912Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/0a/b6f47734e1d7f936cbc52ef8e673d3e08d9c3c8a13d9549c03f978758076/opentelemetry_instrumentation_openai_agents_v2-0.1.0-py3-none-any.whl", hash = "sha256:e4e3dfba32bd6eeee0624eca9be54341ab7cc4f7a3bb895354f2f9d6f7afe2f3", size = 25002, upload-time = "2025-10-15T19:04:58.562Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-openai-v2" +version = "2.3b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-semantic-conventions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/4e/21f8cd16ccb471dd217ed85eb817796a10c4f2718ae2c91e752a57180cf0/opentelemetry_instrumentation_openai_v2-2.3b0.tar.gz", hash = "sha256:5de9d70cc9536eea1fe48ea016e0c5f25735fa9a13709076a64b20657fadb6ba", size = 170838, upload-time = "2025-12-24T13:20:58.33Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/02/7ff0a9282520592772a356dd39d1559f3726610ccc3854a2f598b756c66f/opentelemetry_instrumentation_openai_v2-2.3b0-py3-none-any.whl", hash = "sha256:c6aca87be0da0289ea1d8167fea4b0f227ea5ef0e90496e2822121e47340d36a", size = 18053, upload-time = "2025-12-24T13:20:57.233Z" }, +] + [[package]] name = "opentelemetry-instrumentation-psycopg2" version = "0.61b0" @@ -4772,6 +4857,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, ] +[[package]] +name = "opentelemetry-util-genai" +version = "0.3b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-instrumentation", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "opentelemetry-semantic-conventions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/d8/4dd2fb622d26ec45b10ef63eb87fd512f5d7467c7bd35ce390629bd6dff8/opentelemetry_util_genai-0.3b0.tar.gz", hash = "sha256:83e127789a9ad615b8ca65f05fc36955a67ce257b06142bfd46159a3b7ed73d3", size = 31800, upload-time = "2026-02-20T16:16:14.807Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/e5/fada54909e445d7b4007f8b96221d571999efeab9446f3127cc1cebe5e07/opentelemetry_util_genai-0.3b0-py3-none-any.whl", hash = "sha256:ebc2b01bcb891ddc7218452470d189d3321cd742653299ff8e7de45debcfb986", size = 28426, upload-time = "2026-02-20T16:16:12.027Z" }, +] + [[package]] name = "opentelemetry-util-http" version = "0.61b0" From 5bed9f48fb32bb61cdb990317a9b97ce349b39df Mon Sep 17 00:00:00 2001 From: Giles Odigwe Date: Wed, 3 Jun 2026 09:39:30 -0700 Subject: [PATCH 2/4] Address PR review: scope permission handlers, widen type, add test - Shell sample: only approve kind='shell', deny others - URL sample: only approve kind='url', deny others - Use getattr() for kind-specific attributes to satisfy pyright - Widen PermissionHandlerType to accept async handlers (matches SDK) - Add test for _deny_all_permissions return value Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../agent_framework_github_copilot/_agent.py | 6 ++++-- .../tests/test_github_copilot_agent.py | 7 +++++++ .../github_copilot/github_copilot_with_shell.py | 14 +++++++++----- .../github_copilot/github_copilot_with_url.py | 14 +++++++++----- 4 files changed, 29 insertions(+), 12 deletions(-) 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 d68e8238014..51b57913729 100644 --- a/python/packages/github_copilot/agent_framework_github_copilot/_agent.py +++ b/python/packages/github_copilot/agent_framework_github_copilot/_agent.py @@ -58,8 +58,10 @@ DEFAULT_TIMEOUT_SECONDS: float = 60.0 """Default timeout in seconds for Copilot requests.""" -PermissionHandlerType = Callable[[PermissionRequest, dict[str, str]], PermissionRequestResult] -"""Type for permission request handlers.""" +PermissionHandlerType = Callable[ + [PermissionRequest, dict[str, str]], "PermissionRequestResult | Awaitable[PermissionRequestResult]" +] +"""Type for permission request handlers. Supports both sync and async callbacks.""" FunctionApprovalCallback = Callable[[Content], "bool | Awaitable[bool]"] 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 cfef243b984..ca15be30b49 100644 --- a/python/packages/github_copilot/tests/test_github_copilot_agent.py +++ b/python/packages/github_copilot/tests/test_github_copilot_agent.py @@ -1869,6 +1869,13 @@ async def test_get_or_create_session_raises_when_client_not_initialized(self) -> class TestGitHubCopilotAgentPermissions: """Test cases for permission handling.""" + def test_deny_all_permissions_returns_user_not_available(self) -> None: + """Test that the default deny handler returns user-not-available.""" + from agent_framework_github_copilot._agent import _deny_all_permissions + + result = _deny_all_permissions(MagicMock(), {}) + assert result.kind == "user-not-available" + def test_no_permission_handler_when_not_provided(self) -> None: """Test that no handler is set when on_permission_request is not provided.""" agent = GitHubCopilotAgent() 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 c93134f121d..66ead3bf992 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 @@ -14,16 +14,20 @@ import asyncio from agent_framework.github import GitHubCopilotAgent +from copilot.generated.rpc import PermissionDecisionUserNotAvailable from copilot.session import PermissionHandler, PermissionRequestResult from copilot.session_events import PermissionRequest def approve_and_log(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: - """Permission handler that auto-approves and logs shell commands.""" - print(f"\n [Permission: {request.kind}]", flush=True) - if request.full_command_text is not None: - print(f" Command: {request.full_command_text}", flush=True) - return PermissionHandler.approve_all(request, context) + """Permission handler that approves only shell commands and logs them.""" + if request.kind == "shell": + print(f"\n [Permission: {request.kind}]", flush=True) + command = getattr(request, "full_command_text", None) + if command is not None: + print(f" Command: {command}", flush=True) + return PermissionHandler.approve_all(request, context) + return PermissionDecisionUserNotAvailable() async def main() -> None: 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 89b547c9bf4..61fa90cd5d4 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 @@ -14,16 +14,20 @@ import asyncio from agent_framework.github import GitHubCopilotAgent +from copilot.generated.rpc import PermissionDecisionUserNotAvailable from copilot.session import PermissionHandler, PermissionRequestResult from copilot.session_events import PermissionRequest def approve_and_log(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult: - """Permission handler that auto-approves and logs the requested URL.""" - print(f"\n [Permission: {request.kind}]", flush=True) - if request.url is not None: - print(f" URL: {request.url}", flush=True) - return PermissionHandler.approve_all(request, context) + """Permission handler that approves only URL requests and logs them.""" + if request.kind == "url": + print(f"\n [Permission: {request.kind}]", flush=True) + url = getattr(request, "url", None) + if url is not None: + print(f" URL: {url}", flush=True) + return PermissionHandler.approve_all(request, context) + return PermissionDecisionUserNotAvailable() async def main() -> None: From 33c8db113c4c770d0ba4d7112b4e656702748d5b Mon Sep 17 00:00:00 2001 From: Giles Odigwe Date: Wed, 3 Jun 2026 10:01:59 -0700 Subject: [PATCH 3/4] Fix validation script and strengthen test assertion - Update scripts/sample_validation/create_dynamic_workflow_executor.py to use copilot.session_events imports and PermissionHandler.approve_all - Assert isinstance(result, PermissionDecisionUserNotAvailable) instead of stringly-typed kind check Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../github_copilot/tests/test_github_copilot_agent.py | 6 ++++-- .../sample_validation/create_dynamic_workflow_executor.py | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) 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 ca15be30b49..ca59aee2ebb 100644 --- a/python/packages/github_copilot/tests/test_github_copilot_agent.py +++ b/python/packages/github_copilot/tests/test_github_copilot_agent.py @@ -1870,11 +1870,13 @@ class TestGitHubCopilotAgentPermissions: """Test cases for permission handling.""" def test_deny_all_permissions_returns_user_not_available(self) -> None: - """Test that the default deny handler returns user-not-available.""" + """Test that the default deny handler returns PermissionDecisionUserNotAvailable.""" + from copilot.generated.rpc import PermissionDecisionUserNotAvailable + from agent_framework_github_copilot._agent import _deny_all_permissions result = _deny_all_permissions(MagicMock(), {}) - assert result.kind == "user-not-available" + assert isinstance(result, PermissionDecisionUserNotAvailable) def test_no_permission_handler_when_not_provided(self) -> None: """Test that no handler is set when on_permission_request is not provided.""" diff --git a/python/scripts/sample_validation/create_dynamic_workflow_executor.py b/python/scripts/sample_validation/create_dynamic_workflow_executor.py index 01af4080979..6ebe25a8d4e 100644 --- a/python/scripts/sample_validation/create_dynamic_workflow_executor.py +++ b/python/scripts/sample_validation/create_dynamic_workflow_executor.py @@ -14,8 +14,8 @@ handler, ) from agent_framework.github import GitHubCopilotAgent -from copilot.generated.session_events import PermissionRequest -from copilot.session import PermissionRequestResult +from copilot.session import PermissionHandler, PermissionRequestResult +from copilot.session_events import PermissionRequest from pydantic import BaseModel from sample_validation.const import WORKER_COMPLETED from sample_validation.discovery import DiscoveryResult @@ -103,7 +103,7 @@ def prompt_permission( logger.debug( f"[Permission Request: {request.kind}] ({context})Automatically approved for sample validation." ) - return PermissionRequestResult(kind="approved") + return PermissionHandler.approve_all(request, context) class CustomAgentExecutor(Executor): From 8442dc04febfb2f026f0884479dd21add22e20fd Mon Sep 17 00:00:00 2001 From: Giles Odigwe Date: Wed, 3 Jun 2026 14:00:29 -0700 Subject: [PATCH 4/4] Add integration tests for GitHubCopilotAgent Add 6 integration tests mirroring .NET coverage: - Basic non-streaming response - Streaming response - Function tool invocation - Session context (multi-turn) - Session resume by ID - Shell command execution Tests require COPILOT_GITHUB_TOKEN env var (skipped otherwise). Each test cleans up its Copilot session via delete_session. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../tests/test_github_copilot_agent.py | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) 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 ca59aee2ebb..518d5758207 100644 --- a/python/packages/github_copilot/tests/test_github_copilot_agent.py +++ b/python/packages/github_copilot/tests/test_github_copilot_agent.py @@ -2,6 +2,7 @@ # ruff: noqa: E402 +import os import unittest.mock from datetime import datetime, timezone from typing import Any @@ -20,8 +21,10 @@ ContextProvider, HistoryProvider, Message, + tool, ) from agent_framework.exceptions import AgentException +from copilot.session import PermissionHandler from copilot.session_events import ( Data, SessionEvent, @@ -2716,3 +2719,163 @@ def load_skill(skill_name: str) -> str: assert call_kwargs.get("tools") is not None tool_names = [t.name for t in call_kwargs["tools"]] assert "load_skill" in tool_names + + +# --------------------------------------------------------------------------- +# Integration tests — require COPILOT_GITHUB_TOKEN env var +# --------------------------------------------------------------------------- + +skip_if_copilot_integration_tests_disabled = pytest.mark.skipif( + os.getenv("COPILOT_GITHUB_TOKEN", "") == "", + reason="No COPILOT_GITHUB_TOKEN provided; skipping integration tests.", +) + + +@tool(approval_mode="never_require") +def get_weather(location: str) -> str: + """Get the weather for a given location.""" + return f"The weather in {location} is sunny with a high of 25C." + + +@pytest.mark.flaky +@pytest.mark.integration +@skip_if_copilot_integration_tests_disabled +async def test_integration_run_with_simple_prompt_returns_response() -> None: + """Integration test: basic non-streaming response.""" + agent = GitHubCopilotAgent( + instructions="You are a helpful assistant. Keep your answers short.", + default_options={"on_permission_request": PermissionHandler.approve_all}, + ) + + async with agent: + session = agent.create_session() + response = await agent.run("What is 2 + 2? Answer with just the number.", session=session) + + assert response is not None + assert len(response.messages) > 0 + assert "4" in response.text + + if session.service_session_id and agent._client: + await agent._client.delete_session(session.service_session_id) + + +@pytest.mark.flaky +@pytest.mark.integration +@skip_if_copilot_integration_tests_disabled +async def test_integration_run_streaming_returns_updates() -> None: + """Integration test: streaming response yields updates.""" + agent = GitHubCopilotAgent( + instructions="You are a helpful assistant. Keep your answers short.", + default_options={"on_permission_request": PermissionHandler.approve_all}, + ) + + async with agent: + session = agent.create_session() + updates = [] + async for chunk in agent.run("Count from 1 to 5.", stream=True, session=session): + updates.append(chunk) + + assert len(updates) > 0 + full_text = "".join(u.text for u in updates if u.text) + assert len(full_text) > 0 + + if session.service_session_id and agent._client: + await agent._client.delete_session(session.service_session_id) + + +@pytest.mark.flaky +@pytest.mark.integration +@skip_if_copilot_integration_tests_disabled +async def test_integration_run_with_function_tool_invokes_tool() -> None: + """Integration test: function tool is invoked by the agent.""" + agent = GitHubCopilotAgent( + instructions="You are a helpful weather agent. Use the get_weather tool to answer weather questions.", + tools=[get_weather], + default_options={"on_permission_request": PermissionHandler.approve_all}, + ) + + async with agent: + session = agent.create_session() + response = await agent.run("What's the weather like in Seattle?", session=session) + + assert response is not None + assert len(response.messages) > 0 + assert any(word in response.text.lower() for word in ["sunny", "25", "weather", "seattle"]) + + if session.service_session_id and agent._client: + await agent._client.delete_session(session.service_session_id) + + +@pytest.mark.flaky +@pytest.mark.integration +@skip_if_copilot_integration_tests_disabled +async def test_integration_run_with_session_maintains_context() -> None: + """Integration test: session maintains conversation context across turns.""" + agent = GitHubCopilotAgent( + instructions="You are a helpful assistant. Keep your answers short.", + default_options={"on_permission_request": PermissionHandler.approve_all}, + ) + + async with agent: + session = agent.create_session() + + response1 = await agent.run("My name is Alice.", session=session) + assert response1 is not None + + response2 = await agent.run("What is my name?", session=session) + + assert response2 is not None + assert "alice" in response2.text.lower() + + if session.service_session_id and agent._client: + await agent._client.delete_session(session.service_session_id) + + +@pytest.mark.flaky +@pytest.mark.integration +@skip_if_copilot_integration_tests_disabled +async def test_integration_run_with_session_resume_continues_conversation() -> None: + """Integration test: session can be resumed by ID.""" + agent = GitHubCopilotAgent( + instructions="You are a helpful assistant. Keep your answers short.", + default_options={"on_permission_request": PermissionHandler.approve_all}, + ) + + async with agent: + session1 = agent.create_session() + await agent.run("Remember this number: 42.", session=session1) + + session_id = session1.service_session_id + assert session_id is not None + + session2 = AgentSession() + session2.service_session_id = session_id + + response = await agent.run("What number did I ask you to remember?", session=session2) + + assert response is not None + assert "42" in response.text + + if agent._client: + await agent._client.delete_session(session_id) + + +@pytest.mark.flaky +@pytest.mark.integration +@skip_if_copilot_integration_tests_disabled +async def test_integration_run_with_shell_permissions_executes_command() -> None: + """Integration test: shell commands can be executed with permission handler.""" + agent = GitHubCopilotAgent( + instructions="You are a helpful assistant that can execute shell commands.", + default_options={"on_permission_request": PermissionHandler.approve_all}, + ) + + async with agent: + session = agent.create_session() + response = await agent.run("Run a shell command to print 'hello world'", session=session) + + assert response is not None + assert "hello" in response.text.lower() + + if session.service_session_id and agent._client: + await agent._client.delete_session(session.service_session_id)