mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
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:
committed by
GitHub
Unverified
parent
790a759dbf
commit
d4036c5aef
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user