mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Upgrade github-copilot-sdk to v1.0.0b2 with new features (#5665)
* Upgrade github-copilot-sdk to v1.0.0b1 and implement new features - Bump github-copilot-sdk dependency from 0.2.1 to 1.0.0b1 - Fix breaking type renames: ErrorClass -> ToolExecutionCompleteError, Result -> ToolExecutionCompleteResult - Add instruction_directories support in GitHubCopilotOptions (session-level) - Add copilot_home support in GitHubCopilotSettings (client-level) - Add sample: github_copilot_with_instruction_directories.py - Update README with new env var and sample entry - Add 8 new unit tests covering the new features (103 total, 96% coverage) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * mypy fix * small fix * Address PR feedback: fix resume path, remove copilot_home from Options, bump to beta.2 - Forward runtime_options through _resume_session (fixes silent drop of instruction_directories/model/etc on resumed sessions) - Remove copilot_home from GitHubCopilotOptions (client-level setting only consumed at startup, not per-call) - Bump github-copilot-sdk from 1.0.0b1 to 1.0.0b2 - Add test for instruction_directories override on resumed sessions - Update existing resume test to match new _resume_session signature Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
3c1e2c40b8
commit
57fb32efc8
@@ -22,7 +22,13 @@ from agent_framework import (
|
||||
Message,
|
||||
)
|
||||
from agent_framework.exceptions import AgentException
|
||||
from copilot.generated.session_events import Data, ErrorClass, Result, SessionEvent, SessionEventType
|
||||
from copilot.generated.session_events import (
|
||||
Data,
|
||||
SessionEvent,
|
||||
SessionEventType,
|
||||
ToolExecutionCompleteError,
|
||||
ToolExecutionCompleteResult,
|
||||
)
|
||||
from copilot.tools import ToolInvocation, ToolResult
|
||||
|
||||
from agent_framework_github_copilot import GitHubCopilotAgent, GitHubCopilotOptions
|
||||
@@ -212,6 +218,18 @@ class TestGitHubCopilotAgentInit:
|
||||
opts["model"] = "mutated"
|
||||
assert agent._settings.get("model") == "gpt-5.1-mini"
|
||||
|
||||
def test_init_stores_instruction_directories(self) -> None:
|
||||
"""Test that instruction_directories are stored on the agent instance."""
|
||||
agent: GitHubCopilotAgent[GitHubCopilotOptions] = GitHubCopilotAgent(
|
||||
default_options={"instruction_directories": ["/my/instructions"]}
|
||||
)
|
||||
assert agent._instruction_directories == ["/my/instructions"] # type: ignore
|
||||
|
||||
def test_init_without_instruction_directories(self) -> None:
|
||||
"""Test that instruction_directories default to None when not provided."""
|
||||
agent = GitHubCopilotAgent()
|
||||
assert agent._instruction_directories is None # type: ignore
|
||||
|
||||
|
||||
class TestGitHubCopilotAgentLifecycle:
|
||||
"""Test cases for agent lifecycle management."""
|
||||
@@ -294,6 +312,50 @@ class TestGitHubCopilotAgentLifecycle:
|
||||
assert call_args.cli_path == "/custom/path"
|
||||
assert call_args.log_level == "debug"
|
||||
|
||||
async def test_start_passes_copilot_home_to_subprocess_config(self) -> None:
|
||||
"""Test that copilot_home is passed through to SubprocessConfig."""
|
||||
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"}
|
||||
)
|
||||
await agent.start()
|
||||
|
||||
call_args = MockClient.call_args[0][0]
|
||||
assert call_args.copilot_home == "/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."""
|
||||
with patch("agent_framework_github_copilot._agent.CopilotClient") as MockClient:
|
||||
mock_client = MagicMock()
|
||||
mock_client.start = AsyncMock()
|
||||
MockClient.return_value = mock_client
|
||||
|
||||
agent = GitHubCopilotAgent()
|
||||
await agent.start()
|
||||
|
||||
call_args = MockClient.call_args[0][0]
|
||||
assert call_args.copilot_home is None
|
||||
|
||||
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."""
|
||||
with (
|
||||
patch("agent_framework_github_copilot._agent.CopilotClient") as MockClient,
|
||||
patch.dict("os.environ", {"GITHUB_COPILOT_COPILOT_HOME": "/env/copilot/home"}),
|
||||
):
|
||||
mock_client = MagicMock()
|
||||
mock_client.start = AsyncMock()
|
||||
MockClient.return_value = mock_client
|
||||
|
||||
agent = GitHubCopilotAgent()
|
||||
await agent.start()
|
||||
|
||||
call_args = MockClient.call_args[0][0]
|
||||
assert call_args.copilot_home == "/env/copilot/home"
|
||||
|
||||
|
||||
class TestGitHubCopilotAgentRun:
|
||||
"""Test cases for run method."""
|
||||
@@ -537,7 +599,7 @@ class TestGitHubCopilotAgentRunStreaming:
|
||||
"""Test that TOOL_EXECUTION_COMPLETE events produce function_result content."""
|
||||
tool_event_data = MagicMock()
|
||||
tool_event_data.tool_call_id = "call_abc123"
|
||||
tool_event_data.result = Result(content="Sunny, 72°F")
|
||||
tool_event_data.result = ToolExecutionCompleteResult(content="Sunny, 72°F")
|
||||
tool_event_data.success = True
|
||||
tool_event_data.error = None
|
||||
|
||||
@@ -652,9 +714,9 @@ class TestGitHubCopilotAgentRunStreaming:
|
||||
"""Test that a failed tool result surfaces the error as exception."""
|
||||
tool_event_data = MagicMock()
|
||||
tool_event_data.tool_call_id = "call_fail"
|
||||
tool_event_data.result = Result(content="Error: connection timeout")
|
||||
tool_event_data.result = ToolExecutionCompleteResult(content="Error: connection timeout")
|
||||
tool_event_data.success = False
|
||||
tool_event_data.error = ErrorClass(message="connection timeout")
|
||||
tool_event_data.error = ToolExecutionCompleteError(message="connection timeout")
|
||||
|
||||
tool_event = SessionEvent(
|
||||
data=tool_event_data,
|
||||
@@ -691,7 +753,7 @@ class TestGitHubCopilotAgentRunStreaming:
|
||||
"""Test that a failed tool result with a string error is surfaced."""
|
||||
tool_event_data = MagicMock()
|
||||
tool_event_data.tool_call_id = "call_fail2"
|
||||
tool_event_data.result = Result(content="")
|
||||
tool_event_data.result = ToolExecutionCompleteResult(content="")
|
||||
tool_event_data.success = False
|
||||
tool_event_data.error = "something went wrong"
|
||||
|
||||
@@ -729,7 +791,7 @@ class TestGitHubCopilotAgentRunStreaming:
|
||||
"""Test that a successful tool result with error field does not propagate exception."""
|
||||
tool_event_data = MagicMock()
|
||||
tool_event_data.tool_call_id = "call_ok"
|
||||
tool_event_data.result = Result(content="partial result")
|
||||
tool_event_data.result = ToolExecutionCompleteResult(content="partial result")
|
||||
tool_event_data.success = True
|
||||
tool_event_data.error = "some warning"
|
||||
|
||||
@@ -817,7 +879,7 @@ class TestGitHubCopilotAgentRunStreaming:
|
||||
# Tool result event
|
||||
result_data = MagicMock()
|
||||
result_data.tool_call_id = "call_001"
|
||||
result_data.result = Result(content="72°F and sunny")
|
||||
result_data.result = ToolExecutionCompleteResult(content="72°F and sunny")
|
||||
result_data.success = True
|
||||
result_data.error = None
|
||||
tool_result_event = SessionEvent(
|
||||
@@ -882,9 +944,12 @@ class TestGitHubCopilotAgentSessionManagement:
|
||||
mock_session.session_id,
|
||||
on_permission_request=unittest.mock.ANY,
|
||||
streaming=unittest.mock.ANY,
|
||||
model=unittest.mock.ANY,
|
||||
system_message=unittest.mock.ANY,
|
||||
tools=unittest.mock.ANY,
|
||||
mcp_servers=unittest.mock.ANY,
|
||||
provider=unittest.mock.ANY,
|
||||
instruction_directories=unittest.mock.ANY,
|
||||
)
|
||||
|
||||
async def test_session_config_includes_model(
|
||||
@@ -1016,6 +1081,100 @@ class TestGitHubCopilotAgentSessionManagement:
|
||||
assert "tools" in config
|
||||
assert "on_permission_request" in config
|
||||
|
||||
async def test_instruction_directories_passed_to_create_session(
|
||||
self,
|
||||
mock_client: MagicMock,
|
||||
mock_session: MagicMock,
|
||||
) -> None:
|
||||
"""Test that instruction_directories are passed through to create_session."""
|
||||
agent: GitHubCopilotAgent[GitHubCopilotOptions] = GitHubCopilotAgent(
|
||||
client=mock_client,
|
||||
default_options={"instruction_directories": ["/path/to/instructions", "/other/path"]},
|
||||
)
|
||||
await agent.start()
|
||||
|
||||
await agent._get_or_create_session(AgentSession()) # type: ignore
|
||||
|
||||
call_args = mock_client.create_session.call_args
|
||||
config = call_args.kwargs
|
||||
assert config["instruction_directories"] == ["/path/to/instructions", "/other/path"]
|
||||
|
||||
async def test_instruction_directories_runtime_override(
|
||||
self,
|
||||
mock_client: MagicMock,
|
||||
mock_session: MagicMock,
|
||||
) -> None:
|
||||
"""Test that runtime instruction_directories take precedence over defaults."""
|
||||
agent: GitHubCopilotAgent[GitHubCopilotOptions] = GitHubCopilotAgent(
|
||||
client=mock_client,
|
||||
default_options={"instruction_directories": ["/default/path"]},
|
||||
)
|
||||
await agent.start()
|
||||
|
||||
runtime_options: GitHubCopilotOptions = {"instruction_directories": ["/runtime/path"]}
|
||||
await agent._get_or_create_session(AgentSession(), runtime_options=runtime_options) # type: ignore
|
||||
|
||||
call_args = mock_client.create_session.call_args
|
||||
config = call_args.kwargs
|
||||
assert config["instruction_directories"] == ["/runtime/path"]
|
||||
|
||||
async def test_instruction_directories_none_when_not_specified(
|
||||
self,
|
||||
mock_client: MagicMock,
|
||||
mock_session: MagicMock,
|
||||
) -> None:
|
||||
"""Test that instruction_directories is None when not specified."""
|
||||
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.kwargs
|
||||
assert config["instruction_directories"] is None
|
||||
|
||||
async def test_instruction_directories_empty_list_clears_defaults(
|
||||
self,
|
||||
mock_client: MagicMock,
|
||||
mock_session: MagicMock,
|
||||
) -> None:
|
||||
"""Test that an explicit empty list at runtime clears the agent-level defaults."""
|
||||
agent: GitHubCopilotAgent[GitHubCopilotOptions] = GitHubCopilotAgent(
|
||||
client=mock_client,
|
||||
default_options={"instruction_directories": ["/default/path"]},
|
||||
)
|
||||
await agent.start()
|
||||
|
||||
runtime_options: GitHubCopilotOptions = {"instruction_directories": []}
|
||||
await agent._get_or_create_session(AgentSession(), runtime_options=runtime_options) # type: ignore
|
||||
|
||||
call_args = mock_client.create_session.call_args
|
||||
config = call_args.kwargs
|
||||
assert config["instruction_directories"] == []
|
||||
|
||||
async def test_instruction_directories_override_on_resumed_session(
|
||||
self,
|
||||
mock_client: MagicMock,
|
||||
mock_session: MagicMock,
|
||||
) -> None:
|
||||
"""Test that instruction_directories override works on resumed sessions."""
|
||||
agent: GitHubCopilotAgent[GitHubCopilotOptions] = GitHubCopilotAgent(
|
||||
client=mock_client,
|
||||
default_options={"instruction_directories": ["/default/path"]},
|
||||
)
|
||||
await agent.start()
|
||||
|
||||
# Simulate a session that already has a service_session_id (resume path)
|
||||
session = AgentSession()
|
||||
session.service_session_id = "existing-session-id"
|
||||
|
||||
runtime_options: GitHubCopilotOptions = {"instruction_directories": ["/override/path"]}
|
||||
await agent._get_or_create_session(session, runtime_options=runtime_options) # type: ignore
|
||||
|
||||
call_args = mock_client.resume_session.call_args
|
||||
config = call_args.kwargs
|
||||
assert config["instruction_directories"] == ["/override/path"]
|
||||
|
||||
|
||||
class TestGitHubCopilotAgentMCPServers:
|
||||
"""Test cases for MCP server configuration."""
|
||||
|
||||
Reference in New Issue
Block a user