From 04cca1e7f383b5964398384a06849acd69fbf3f4 Mon Sep 17 00:00:00 2001 From: Christoph Koehler Date: Mon, 25 Aug 2025 17:18:40 -0600 Subject: [PATCH] feat: add image headers for Copilot Fixes #13696. See issue for details. --- .../github_copilot/chat/transformation.py | 28 ++++++ .../test_github_copilot_transformation.py | 96 +++++++++++++++++++ 2 files changed, 124 insertions(+) diff --git a/litellm/llms/github_copilot/chat/transformation.py b/litellm/llms/github_copilot/chat/transformation.py index 4526e6247b4..4aa6063570d 100644 --- a/litellm/llms/github_copilot/chat/transformation.py +++ b/litellm/llms/github_copilot/chat/transformation.py @@ -75,6 +75,10 @@ class GithubCopilotConfig(OpenAIConfig): initiator = self._determine_initiator(messages) validated_headers["X-Initiator"] = initiator + # Add Copilot-Vision-Request header if request contains images + if self._has_vision_content(messages): + validated_headers["Copilot-Vision-Request"] = "true" + return validated_headers def _determine_initiator(self, messages: List[AllMessageValues]) -> str: @@ -87,3 +91,27 @@ class GithubCopilotConfig(OpenAIConfig): if role in ["tool", "assistant"]: return "agent" return "user" + + def _has_vision_content(self, messages: List[AllMessageValues]) -> bool: + """ + Check if any message contains vision content (images). + Returns True if any message has content with vision-related types, otherwise False. + + Checks for: + - image_url content type (OpenAI format) + - Content items with type 'image_url' + """ + for message in messages: + content = message.get("content") + if isinstance(content, list): + # Check if any content item indicates vision content + for content_item in content: + if isinstance(content_item, dict): + # Check for image_url field (direct image URL) + if "image_url" in content_item: + return True + # Check for type field indicating image content + content_type = content_item.get("type") + if content_type == "image_url": + return True + return False diff --git a/tests/test_litellm/llms/github_copilot/test_github_copilot_transformation.py b/tests/test_litellm/llms/github_copilot/test_github_copilot_transformation.py index f21c123579d..1c99b4f9f59 100644 --- a/tests/test_litellm/llms/github_copilot/test_github_copilot_transformation.py +++ b/tests/test_litellm/llms/github_copilot/test_github_copilot_transformation.py @@ -362,3 +362,99 @@ def test_x_initiator_header_system_only_messages(): ) assert headers["X-Initiator"] == "user" + + +def test_copilot_vision_request_header_with_image(): + """Test that Copilot-Vision-Request header is added when messages contain images""" + 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": [ + {"type": "text", "text": "What's in this image?"}, + { + "type": "image_url", + "image_url": {"url": "data:image/jpeg;base64,abc123"} + } + ] + } + ] + + headers = config.validate_environment( + headers={}, + model="github_copilot/gpt-4-vision-preview", + messages=messages, + optional_params={}, + litellm_params={}, + api_key=None, + api_base=None, + ) + + assert headers["Copilot-Vision-Request"] == "true" + assert headers["X-Initiator"] == "user" + + +def test_copilot_vision_request_header_text_only(): + """Test that Copilot-Vision-Request header is not added for text-only messages""" + 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": "Just a text message"}, + ] + + headers = config.validate_environment( + headers={}, + model="github_copilot/gpt-4", + messages=messages, + optional_params={}, + litellm_params={}, + api_key=None, + api_base=None, + ) + + assert "Copilot-Vision-Request" not in headers + assert headers["X-Initiator"] == "user" + + +def test_copilot_vision_request_header_with_type_image_url(): + """Test that Copilot-Vision-Request header is added for content with type: image_url""" + 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": [ + {"type": "text", "text": "Analyze this image"}, + {"type": "image_url", "image_url": {"url": "https://example.com/image.jpg"}} + ] + } + ] + + headers = config.validate_environment( + headers={}, + model="github_copilot/gpt-4-vision-preview", + messages=messages, + optional_params={}, + litellm_params={}, + api_key=None, + api_base=None, + ) + + assert headers["Copilot-Vision-Request"] == "true" + assert headers["X-Initiator"] == "user"