Forward provider config to SessionConfig in GitHubCopilotAgent (fixes #5190) (#5195)

Co-authored-by: Sergey Borisov <sergey.borisov@dataimpact.io>
This commit is contained in:
S3rj
2026-04-16 00:08:01 +02:00
committed by GitHub
Unverified
parent 68b93641b6
commit eab7f09d03
2 changed files with 205 additions and 1 deletions
@@ -31,7 +31,7 @@ from agent_framework.exceptions import AgentException
try:
from copilot import CopilotClient, CopilotSession, SubprocessConfig
from copilot.generated.session_events import PermissionRequest, SessionEvent, SessionEventType
from copilot.session import MCPServerConfig, PermissionRequestResult, SystemMessageConfig
from copilot.session import MCPServerConfig, PermissionRequestResult, ProviderConfig, SystemMessageConfig
from copilot.tools import Tool as CopilotTool
from copilot.tools import ToolInvocation, ToolResult
except ImportError as _copilot_import_error:
@@ -120,6 +120,12 @@ class GitHubCopilotOptions(TypedDict, total=False):
Supports both local (stdio) and remote (HTTP/SSE) servers.
"""
provider: ProviderConfig
"""Custom API provider configuration for BYOK (Bring Your Own Key) scenarios.
Allows routing requests through your own OpenAI, Azure, or Anthropic endpoint
instead of the default GitHub Copilot backend.
"""
OptionsT = TypeVar(
"OptionsT",
@@ -232,6 +238,7 @@ class GitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
log_level = opts.pop("log_level", None)
on_permission_request: PermissionHandlerType | None = opts.pop("on_permission_request", None)
mcp_servers: dict[str, MCPServerConfig] | None = opts.pop("mcp_servers", None)
provider: ProviderConfig | None = opts.pop("provider", None)
self._settings = load_settings(
GitHubCopilotSettings,
@@ -247,6 +254,7 @@ class GitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
self._tools = normalize_tools(tools)
self._permission_handler = on_permission_request
self._mcp_servers = mcp_servers
self._provider = provider
self._default_options = opts
self._started = False
@@ -730,6 +738,7 @@ class GitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
opts.get("on_permission_request") or self._permission_handler or _deny_all_permissions
)
mcp_servers = opts.get("mcp_servers") or self._mcp_servers or None
provider = opts.get("provider") or self._provider or None
tools = self._prepare_tools(self._tools) if self._tools else None
return await self._client.create_session(
@@ -739,6 +748,7 @@ class GitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
system_message=system_message or None,
tools=tools or None,
mcp_servers=mcp_servers or None,
provider=provider or None,
)
async def _resume_session(self, session_id: str, streaming: bool) -> CopilotSession:
@@ -755,4 +765,5 @@ class GitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
streaming=streaming,
tools=tools or None,
mcp_servers=self._mcp_servers or None,
provider=self._provider or None,
)
@@ -861,6 +861,7 @@ class TestGitHubCopilotAgentSessionManagement:
streaming=unittest.mock.ANY,
tools=unittest.mock.ANY,
mcp_servers=unittest.mock.ANY,
provider=unittest.mock.ANY,
)
async def test_session_config_includes_model(
@@ -1084,6 +1085,198 @@ class TestGitHubCopilotAgentMCPServers:
assert config["mcp_servers"] is None
class TestGitHubCopilotAgentProvider:
"""Test cases for provider configuration (BYOK / Managed Identity)."""
async def test_provider_passed_to_create_session(
self,
mock_client: MagicMock,
) -> None:
"""Test that provider config is passed through to create_session."""
from copilot.session import ProviderConfig
provider: ProviderConfig = {
"type": "azure",
"base_url": "https://my-resource.openai.azure.com",
"bearer_token": "test-token",
}
agent: GitHubCopilotAgent[GitHubCopilotOptions] = GitHubCopilotAgent(
client=mock_client,
default_options={"provider": provider},
)
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["provider"]["type"] == "azure"
assert config["provider"]["base_url"] == "https://my-resource.openai.azure.com"
assert config["provider"]["bearer_token"] == "test-token"
async def test_provider_passed_to_resume_session(
self,
mock_client: MagicMock,
) -> None:
"""Test that provider config is passed through to resume_session."""
from copilot.session import ProviderConfig
provider: ProviderConfig = {
"type": "azure",
"base_url": "https://my-resource.openai.azure.com",
"bearer_token": "test-token",
}
agent: GitHubCopilotAgent[GitHubCopilotOptions] = GitHubCopilotAgent(
client=mock_client,
default_options={"provider": provider},
)
await agent.start()
session = AgentSession()
session.service_session_id = "existing-session-id"
await agent._get_or_create_session(session) # type: ignore
mock_client.resume_session.assert_called_once()
call_args = mock_client.resume_session.call_args
config = call_args.kwargs
assert config["provider"]["type"] == "azure"
async def test_session_config_excludes_provider_when_not_set(
self,
mock_client: MagicMock,
) -> None:
"""Test that provider is None in session config when not set."""
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["provider"] is None
async def test_resume_session_excludes_provider_when_not_set(
self,
mock_client: MagicMock,
) -> None:
"""Test that provider is None in resume session config when not set."""
agent = GitHubCopilotAgent(client=mock_client)
await agent.start()
session = AgentSession()
session.service_session_id = "existing-session-id"
await agent._get_or_create_session(session) # type: ignore
call_args = mock_client.resume_session.call_args
config = call_args.kwargs
assert config["provider"] is None
async def test_runtime_provider_takes_precedence(
self,
mock_client: MagicMock,
) -> None:
"""Test that runtime provider options override default_options provider."""
from copilot.session import ProviderConfig
default_provider: ProviderConfig = {
"type": "azure",
"base_url": "https://default.openai.azure.com",
"bearer_token": "default-token",
}
runtime_provider: ProviderConfig = {
"type": "openai",
"base_url": "https://runtime.openai.com",
"api_key": "runtime-key",
}
agent: GitHubCopilotAgent[GitHubCopilotOptions] = GitHubCopilotAgent(
client=mock_client,
default_options={"provider": default_provider},
)
await agent.start()
await agent._get_or_create_session( # type: ignore
AgentSession(),
runtime_options={"provider": runtime_provider},
)
call_args = mock_client.create_session.call_args
config = call_args.kwargs
assert config["provider"]["type"] == "openai"
assert config["provider"]["base_url"] == "https://runtime.openai.com"
async def test_provider_not_leaked_into_default_options(
self,
mock_client: MagicMock,
) -> None:
"""Test that provider is popped from opts and not left in _default_options."""
from copilot.session import ProviderConfig
provider: ProviderConfig = {
"type": "azure",
"base_url": "https://my-resource.openai.azure.com",
"bearer_token": "test-token",
}
agent: GitHubCopilotAgent[GitHubCopilotOptions] = GitHubCopilotAgent(
client=mock_client,
default_options={"provider": provider, "model": "gpt-5"},
)
assert "provider" not in agent._default_options
assert agent._provider is not None
assert agent._provider["type"] == "azure"
async def test_provider_coexists_with_other_options(
self,
mock_client: MagicMock,
) -> None:
"""Test that provider works alongside model, tools, and mcp_servers."""
from copilot.session import MCPServerConfig, ProviderConfig
provider: ProviderConfig = {
"type": "azure",
"base_url": "https://my-resource.openai.azure.com",
"bearer_token": "test-token",
}
mcp_servers: dict[str, MCPServerConfig] = {
"test-server": {
"type": "stdio",
"command": "echo",
"args": ["hello"],
"tools": ["*"],
},
}
def my_tool(arg: str) -> str:
"""A test tool."""
return arg
agent: GitHubCopilotAgent[GitHubCopilotOptions] = GitHubCopilotAgent(
client=mock_client,
tools=[my_tool],
default_options={
"model": "gpt-5",
"provider": provider,
"mcp_servers": mcp_servers,
},
)
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["provider"]["type"] == "azure"
assert config["model"] == "gpt-5"
assert config["mcp_servers"] is not None
assert config["tools"] is not None
class TestGitHubCopilotAgentToolConversion:
"""Test cases for tool conversion."""