Python: Migrate GitHub Copilot package to SDK 0.2.x (#5107)

* Python: Migrate GitHub Copilot package to SDK 0.2.x

Replace all imports from the non-existent copilot.types module with
correct SDK 0.2.x module paths (copilot.session, copilot.client,
copilot.tools, copilot.generated.session_events). Fix PermissionRequest
attribute access from dict-style .get() to dataclass attribute access.
Add OTel telemetry support to Copilot samples via configure_otel_providers
and document new telemetry environment variables in samples README.

* Python: Fix remaining copilot.types import in sample validation script

* Python: Include model in default_options for telemetry span attributes

* Python: Address review feedback on log_level and session kwargs typing

* Python: Scope PR to SDK 0.2.x migration only, remove net-new OTel features

- Remove RawGitHubCopilotAgent split and AgentTelemetryLayer inheritance
- Remove TelemetryConfig plumbing and OTLP/file telemetry settings
- Remove configure_otel_providers() calls from samples
- Remove telemetry env var rows from samples README
- Retain only: import path fixes, PermissionRequest attribute access fix,
  log_level default fix, session kwargs typed fix, dependency pin

* Python: Update tests for SDK 0.2.x API changes

- SubprocessConfig replaces CopilotClientOptions dict
- create_session and resume_session now use keyword args
- send and send_and_wait take plain string prompt instead of MessageOptions
- on_permission_request is always required; deny-all fallback replaces omission

* Python: Pin github-copilot-sdk to >=0.2.0,<=0.2.0

Tighten the upper bound from <0.3.0 to <=0.2.0 to avoid pulling in 0.2.1+
which has breaking API changes relative to 0.2.0. The lower bound stays at
>=0.2.0 since this migration requires the 0.2.x import paths; 0.1.x would
fail at import time.

* Python: Pin github-copilot-sdk to >=0.2.1,<=0.2.1

---------

Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
This commit is contained in:
Dineshsuriya D
2026-04-10 06:37:14 +05:30
committed by GitHub
Unverified
parent 790a759dbf
commit d4036c5aef
12 changed files with 112 additions and 111 deletions
@@ -23,7 +23,7 @@ from agent_framework import (
)
from agent_framework.exceptions import AgentException
from copilot.generated.session_events import Data, ErrorClass, Result, SessionEvent, SessionEventType
from copilot.types import ToolInvocation, ToolResult
from copilot.tools import ToolInvocation, ToolResult
from agent_framework_github_copilot import GitHubCopilotAgent, GitHubCopilotOptions
@@ -268,8 +268,8 @@ class TestGitHubCopilotAgentLifecycle:
await agent.start()
call_args = MockClient.call_args[0][0]
assert call_args["cli_path"] == "/custom/path"
assert call_args["log_level"] == "debug"
assert call_args.cli_path == "/custom/path"
assert call_args.log_level == "debug"
class TestGitHubCopilotAgentRun:
@@ -855,7 +855,13 @@ class TestGitHubCopilotAgentSessionManagement:
await agent.run("World", session=session)
mock_client.create_session.assert_called_once()
mock_client.resume_session.assert_called_once_with(mock_session.session_id, unittest.mock.ANY)
mock_client.resume_session.assert_called_once_with(
mock_session.session_id,
on_permission_request=unittest.mock.ANY,
streaming=unittest.mock.ANY,
tools=unittest.mock.ANY,
mcp_servers=unittest.mock.ANY,
)
async def test_session_config_includes_model(
self,
@@ -871,7 +877,7 @@ class TestGitHubCopilotAgentSessionManagement:
await agent._get_or_create_session(AgentSession()) # type: ignore
call_args = mock_client.create_session.call_args
config = call_args[0][0]
config = call_args.kwargs
assert config["model"] == "claude-sonnet-4"
async def test_session_config_includes_instructions(
@@ -889,7 +895,7 @@ class TestGitHubCopilotAgentSessionManagement:
await agent._get_or_create_session(AgentSession()) # type: ignore
call_args = mock_client.create_session.call_args
config = call_args[0][0]
config = call_args.kwargs
assert config["system_message"]["mode"] == "append"
assert config["system_message"]["content"] == "You are a helpful assistant."
@@ -914,7 +920,7 @@ class TestGitHubCopilotAgentSessionManagement:
)
call_args = mock_client.create_session.call_args
config = call_args[0][0]
config = call_args.kwargs
assert config["system_message"]["mode"] == "replace"
assert config["system_message"]["content"] == "Runtime instructions"
@@ -930,7 +936,7 @@ class TestGitHubCopilotAgentSessionManagement:
await agent._get_or_create_session(AgentSession(), streaming=True) # type: ignore
call_args = mock_client.create_session.call_args
config = call_args[0][0]
config = call_args.kwargs
assert config["streaming"] is True
async def test_resume_session_with_existing_service_session_id(
@@ -958,7 +964,8 @@ class TestGitHubCopilotAgentSessionManagement:
mock_session: MagicMock,
) -> None:
"""Test that resumed session config includes tools and permission handler."""
from copilot.types import PermissionRequest, PermissionRequestResult
from copilot.generated.session_events import PermissionRequest
from copilot.session import PermissionRequestResult
def my_handler(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult:
return PermissionRequestResult(kind="approved")
@@ -981,7 +988,7 @@ class TestGitHubCopilotAgentSessionManagement:
mock_client.resume_session.assert_called_once()
call_args = mock_client.resume_session.call_args
config = call_args[0][1]
config = call_args.kwargs
assert "tools" in config
assert "on_permission_request" in config
@@ -995,7 +1002,7 @@ class TestGitHubCopilotAgentMCPServers:
mock_session: MagicMock,
) -> None:
"""Test that mcp_servers are passed through to create_session config."""
from copilot.types import MCPServerConfig
from copilot.session import MCPServerConfig
mcp_servers: dict[str, MCPServerConfig] = {
"filesystem": {
@@ -1020,7 +1027,7 @@ class TestGitHubCopilotAgentMCPServers:
await agent._get_or_create_session(AgentSession()) # type: ignore
call_args = mock_client.create_session.call_args
config = call_args[0][0]
config = call_args.kwargs
assert "mcp_servers" in config
assert "filesystem" in config["mcp_servers"]
assert "remote" in config["mcp_servers"]
@@ -1033,7 +1040,7 @@ class TestGitHubCopilotAgentMCPServers:
mock_session: MagicMock,
) -> None:
"""Test that mcp_servers are passed through to resume_session config."""
from copilot.types import MCPServerConfig
from copilot.session import MCPServerConfig
mcp_servers: dict[str, MCPServerConfig] = {
"test-server": {
@@ -1057,7 +1064,7 @@ class TestGitHubCopilotAgentMCPServers:
mock_client.resume_session.assert_called_once()
call_args = mock_client.resume_session.call_args
config = call_args[0][1]
config = call_args.kwargs
assert "mcp_servers" in config
assert "test-server" in config["mcp_servers"]
@@ -1073,8 +1080,8 @@ class TestGitHubCopilotAgentMCPServers:
await agent._get_or_create_session(AgentSession()) # type: ignore
call_args = mock_client.create_session.call_args
config = call_args[0][0]
assert "mcp_servers" not in config
config = call_args.kwargs
assert config["mcp_servers"] is None
class TestGitHubCopilotAgentToolConversion:
@@ -1097,7 +1104,7 @@ class TestGitHubCopilotAgentToolConversion:
await agent._get_or_create_session(AgentSession()) # type: ignore
call_args = mock_client.create_session.call_args
config = call_args[0][0]
config = call_args.kwargs
assert "tools" in config
assert len(config["tools"]) == 1
assert config["tools"][0].name == "my_tool"
@@ -1120,7 +1127,7 @@ class TestGitHubCopilotAgentToolConversion:
await agent._get_or_create_session(AgentSession()) # type: ignore
call_args = mock_client.create_session.call_args
config = call_args[0][0]
config = call_args.kwargs
copilot_tool = config["tools"][0]
result = await copilot_tool.handler(ToolInvocation(arguments={"arg": "test"}))
@@ -1146,7 +1153,7 @@ class TestGitHubCopilotAgentToolConversion:
await agent._get_or_create_session(AgentSession()) # type: ignore
call_args = mock_client.create_session.call_args
config = call_args[0][0]
config = call_args.kwargs
copilot_tool = config["tools"][0]
result = await copilot_tool.handler(ToolInvocation(arguments={"arg": "test"}))
@@ -1173,7 +1180,7 @@ class TestGitHubCopilotAgentToolConversion:
await agent._get_or_create_session(AgentSession()) # type: ignore
call_args = mock_client.create_session.call_args
config = call_args[0][0]
config = call_args.kwargs
copilot_tool = config["tools"][0]
with pytest.raises((TypeError, AttributeError)):
@@ -1196,7 +1203,7 @@ class TestGitHubCopilotAgentToolConversion:
await agent._get_or_create_session(AgentSession()) # type: ignore
call_args = mock_client.create_session.call_args
config = call_args[0][0]
config = call_args.kwargs
copilot_tool = config["tools"][0]
result = await copilot_tool.handler(ToolInvocation(arguments={}))
@@ -1210,7 +1217,7 @@ class TestGitHubCopilotAgentToolConversion:
mock_client: MagicMock,
) -> None:
"""Test that CopilotTool instances are passed through as-is."""
from copilot.types import Tool as CopilotTool
from copilot.tools import Tool as CopilotTool
async def tool_handler(invocation: Any) -> Any:
return {"text_result_for_llm": "result", "result_type": "success"}
@@ -1234,7 +1241,7 @@ class TestGitHubCopilotAgentToolConversion:
) -> None:
"""Test that mixed tool types are handled correctly."""
from agent_framework import tool
from copilot.types import Tool as CopilotTool
from copilot.tools import Tool as CopilotTool
@tool(approval_mode="never_require")
def my_function(arg: str) -> str:
@@ -1317,10 +1324,11 @@ class TestGitHubCopilotAgentPermissions:
def test_permission_handler_set_when_provided(self) -> None:
"""Test that a handler is set when on_permission_request is provided."""
from copilot.types import PermissionRequest, PermissionRequestResult
from copilot.generated.session_events import PermissionRequest
from copilot.session import PermissionRequestResult
def approve_shell(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult:
if request.get("kind") == "shell":
if request.kind == "shell":
return PermissionRequestResult(kind="approved")
return PermissionRequestResult(kind="denied-interactively-by-user")
@@ -1335,10 +1343,11 @@ class TestGitHubCopilotAgentPermissions:
mock_session: MagicMock,
) -> None:
"""Test that session config includes permission handler when provided."""
from copilot.types import PermissionRequest, PermissionRequestResult
from copilot.generated.session_events import PermissionRequest
from copilot.session import PermissionRequestResult
def approve_shell_read(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult:
if request.get("kind") in ("shell", "read"):
if request.kind in ("shell", "read"):
return PermissionRequestResult(kind="approved")
return PermissionRequestResult(kind="denied-interactively-by-user")
@@ -1351,24 +1360,29 @@ class TestGitHubCopilotAgentPermissions:
await agent._get_or_create_session(AgentSession()) # type: ignore
call_args = mock_client.create_session.call_args
config = call_args[0][0]
config = call_args.kwargs
assert "on_permission_request" in config
assert config["on_permission_request"] is not None
async def test_session_config_excludes_permission_handler_when_not_set(
async def test_session_config_uses_deny_all_when_no_permission_handler_set(
self,
mock_client: MagicMock,
mock_session: MagicMock,
) -> None:
"""Test that session config does not include permission handler when not set."""
"""Test that session config uses deny-all handler when no permission handler is set.
In SDK 0.2.x, on_permission_request is required by create_session, so the agent
always falls back to _deny_all_permissions when no handler is provided.
"""
agent = GitHubCopilotAgent(client=mock_client)
await agent.start()
await agent._get_or_create_session(AgentSession()) # type: ignore
call_args = mock_client.create_session.call_args
config = call_args[0][0]
assert "on_permission_request" not in config
config = call_args.kwargs
assert "on_permission_request" in config
assert config["on_permission_request"] is not None
class SpyContextProvider(ContextProvider):
@@ -1454,7 +1468,7 @@ class TestGitHubCopilotAgentContextProviders:
session = agent.create_session()
await agent.run("Hello", session=session)
sent_prompt = mock_session.send_and_wait.call_args[0][0]["prompt"]
sent_prompt = mock_session.send_and_wait.call_args[0][0]
assert "Injected by spy provider" in sent_prompt
async def test_after_run_receives_response(
@@ -1547,7 +1561,7 @@ class TestGitHubCopilotAgentContextProviders:
async for _ in agent.run("Hello", stream=True, session=session):
pass
sent_prompt = mock_session.send.call_args[0][0]["prompt"]
sent_prompt = mock_session.send.call_args[0][0]
assert "Injected by spy provider" in sent_prompt
async def test_context_preserved_across_runs(
@@ -1608,7 +1622,7 @@ class TestGitHubCopilotAgentContextProviders:
session = agent.create_session()
await agent.run("Hello", session=session)
sent_prompt = mock_session.send_and_wait.call_args[0][0]["prompt"]
sent_prompt = mock_session.send_and_wait.call_args[0][0]
assert "History message" in sent_prompt
assert "Hello" in sent_prompt
@@ -1659,7 +1673,7 @@ class TestGitHubCopilotAgentContextProviders:
async for _ in agent.run("Hello", stream=True, session=session):
pass
sent_prompt = mock_session.send.call_args[0][0]["prompt"]
sent_prompt = mock_session.send.call_args[0][0]
assert "History message" in sent_prompt
assert "Hello" in sent_prompt