fix: add X-Initiator header for GitHub Copilot to reduce premium requests (#13016)

- Implement X-Initiator header logic in GithubCopilotConfig.validate_environment()
- Set header to "agent" when messages contain agent or tool roles, "user" otherwise
- Reduces unnecessary premium Copilot API usage for non-user calls

Fixes #12859
This commit is contained in:
Christoph Koehler
2025-07-28 09:55:24 -07:00
committed by GitHub
parent 2da6d457ef
commit 8f7d896e26
2 changed files with 227 additions and 3 deletions
@@ -1,7 +1,8 @@
from typing import Any, Optional, Tuple, cast
from typing import Any, Optional, Tuple, cast, List
from litellm.exceptions import AuthenticationError
from litellm.llms.openai.openai import OpenAIConfig
from litellm.types.llms.openai import AllMessageValues
from ..authenticator import Authenticator
from ..common_utils import GetAPIKeyError
@@ -9,6 +10,7 @@ from ..common_utils import GetAPIKeyError
class GithubCopilotConfig(OpenAIConfig):
GITHUB_COPILOT_API_BASE = "https://api.githubcopilot.com/"
def __init__(
self,
api_key: Optional[str] = None,
@@ -25,7 +27,9 @@ class GithubCopilotConfig(OpenAIConfig):
api_key: Optional[str],
custom_llm_provider: str,
) -> Tuple[Optional[str], Optional[str], str]:
dynamic_api_base = self.authenticator.get_api_base() or self.GITHUB_COPILOT_API_BASE
dynamic_api_base = (
self.authenticator.get_api_base() or self.GITHUB_COPILOT_API_BASE
)
try:
dynamic_api_key = self.authenticator.get_api_key()
except GetAPIKeyError as e:
@@ -42,9 +46,44 @@ class GithubCopilotConfig(OpenAIConfig):
model: str,
):
import litellm
disable_copilot_system_to_assistant = litellm.disable_copilot_system_to_assistant
disable_copilot_system_to_assistant = (
litellm.disable_copilot_system_to_assistant
)
if not disable_copilot_system_to_assistant:
for message in messages:
if "role" in message and message["role"] == "system":
cast(Any, message)["role"] = "assistant"
return messages
def validate_environment(
self,
headers: dict,
model: str,
messages: List[AllMessageValues],
optional_params: dict,
litellm_params: dict,
api_key: Optional[str] = None,
api_base: Optional[str] = None,
) -> dict:
# Get base headers from parent
validated_headers = super().validate_environment(
headers, model, messages, optional_params, litellm_params, api_key, api_base
)
# Add X-Initiator header based on message roles
initiator = self._determine_initiator(messages)
validated_headers["X-Initiator"] = initiator
return validated_headers
def _determine_initiator(self, messages: List[AllMessageValues]) -> str:
"""
Determine if request is user or agent initiated based on message roles.
Returns 'agent' if any message has role 'tool' or 'assistant', otherwise 'user'.
"""
for message in messages:
role = message.get("role")
if role in ["tool", "assistant"]:
return "agent"
return "user"
@@ -177,3 +177,188 @@ def test_transform_messages_disable_copilot_system_to_assistant(monkeypatch):
litellm.disable_copilot_system_to_assistant = original_flag
def test_x_initiator_header_user_request():
"""Test that user-only messages result in X-Initiator: user header"""
config = GithubCopilotConfig()
# Mock the authenticator
config.authenticator = MagicMock()
config.authenticator.get_api_key.return_value = "gh.test-key-123"
config.authenticator.get_api_base.return_value = None
messages = [
{"role": "system", "content": "You are an assistant."},
{"role": "user", "content": "Hello!"},
]
headers = config.validate_environment(
headers={},
model="github_copilot/gpt-4",
messages=messages,
optional_params={},
litellm_params={},
api_key=None,
api_base=None,
)
assert headers["X-Initiator"] == "user"
def test_x_initiator_header_agent_request_with_assistant():
"""Test that messages with assistant role result in X-Initiator: agent header"""
config = GithubCopilotConfig()
# Mock the authenticator
config.authenticator = MagicMock()
config.authenticator.get_api_key.return_value = "gh.test-key-123"
config.authenticator.get_api_base.return_value = None
messages = [
{"role": "system", "content": "You are an assistant."},
{"role": "assistant", "content": "I can help you."},
]
headers = config.validate_environment(
headers={},
model="github_copilot/gpt-4",
messages=messages,
optional_params={},
litellm_params={},
api_key=None,
api_base=None,
)
assert headers["X-Initiator"] == "agent"
def test_x_initiator_header_agent_request_with_tool():
"""Test that messages with tool role result in X-Initiator: agent header"""
config = GithubCopilotConfig()
# Mock the authenticator
config.authenticator = MagicMock()
config.authenticator.get_api_key.return_value = "gh.test-key-123"
config.authenticator.get_api_base.return_value = None
messages = [
{"role": "system", "content": "You are an assistant."},
{"role": "tool", "content": "Tool response.", "tool_call_id": "123"},
]
headers = config.validate_environment(
headers={},
model="github_copilot/gpt-4",
messages=messages,
optional_params={},
litellm_params={},
api_key=None,
api_base=None,
)
assert headers["X-Initiator"] == "agent"
def test_x_initiator_header_mixed_messages_with_agent_roles():
"""Test that mixed messages with agent roles (assistant/tool) result in X-Initiator: agent header"""
config = GithubCopilotConfig()
# Mock the authenticator
config.authenticator = MagicMock()
config.authenticator.get_api_key.return_value = "gh.test-key-123"
config.authenticator.get_api_base.return_value = None
messages = [
{"role": "user", "content": "Hello"},
{"role": "assistant", "content": "Previous response."},
{"role": "user", "content": "Follow up question."},
]
headers = config.validate_environment(
headers={},
model="github_copilot/gpt-4",
messages=messages,
optional_params={},
litellm_params={},
api_key=None,
api_base=None,
)
assert headers["X-Initiator"] == "agent"
def test_x_initiator_header_user_only_messages():
"""Test that user + system only messages result in X-Initiator: user header"""
config = GithubCopilotConfig()
# Mock the authenticator
config.authenticator = MagicMock()
config.authenticator.get_api_key.return_value = "gh.test-key-123"
config.authenticator.get_api_base.return_value = None
messages = [
{"role": "system", "content": "You are an assistant."},
{"role": "user", "content": "Hello"},
{"role": "user", "content": "Follow up question."},
]
headers = config.validate_environment(
headers={},
model="github_copilot/gpt-4",
messages=messages,
optional_params={},
litellm_params={},
api_key=None,
api_base=None,
)
assert headers["X-Initiator"] == "user"
def test_x_initiator_header_empty_messages():
"""Test that empty messages result in X-Initiator: user header"""
config = GithubCopilotConfig()
# Mock the authenticator
config.authenticator = MagicMock()
config.authenticator.get_api_key.return_value = "gh.test-key-123"
config.authenticator.get_api_base.return_value = None
messages = []
headers = config.validate_environment(
headers={},
model="github_copilot/gpt-4",
messages=messages,
optional_params={},
litellm_params={},
api_key=None,
api_base=None,
)
assert headers["X-Initiator"] == "user"
def test_x_initiator_header_system_only_messages():
"""Test that system-only messages result in X-Initiator: user header"""
config = GithubCopilotConfig()
# Mock the authenticator
config.authenticator = MagicMock()
config.authenticator.get_api_key.return_value = "gh.test-key-123"
config.authenticator.get_api_base.return_value = None
messages = [
{"role": "system", "content": "You are an assistant."},
]
headers = config.validate_environment(
headers={},
model="github_copilot/gpt-4",
messages=messages,
optional_params={},
litellm_params={},
api_key=None,
api_base=None,
)
assert headers["X-Initiator"] == "user"