fix: resolve Greptile P0 issues for GitHub Copilot premium billing

This commit addresses both P0 issues flagged in Greptile review to achieve
5/5 score (currently 4/5).

**P0 Issue #1: Responses API X-Initiator never set**

Root cause: The X-Initiator header was being set in validate_environment(),
which doesn't have access to the `input` parameter. The _get_input_from_params()
helper checked for `litellm_params.input`, but this attribute doesn't exist in
GenericLiteLLMParams - the `input` parameter is passed separately to
transform_responses_api_request().

Fix: Moved X-Initiator and vision header logic from validate_environment() to
transform_responses_api_request() where the `input` parameter is available.
This ensures the header is correctly set for Responses API requests.

Changes:
- Added transform_responses_api_request() override in
  GithubCopilotResponsesAPIConfig that:
  1. Calculates X-Initiator based on input analysis
  2. Adds copilot-vision-request header if images detected
  3. Calls parent to validate input and handle reasoning items
- Removed broken _get_input_from_params() helper
- Simplified validate_environment() docstring and removed duplicate logic

**P0 Issue #2: Integration test in wrong location**

Root cause: tests/llm_translation/test_github_copilot_billing.py makes real
network calls via litellm.completion(), violating the repository rule that
tests/llm_translation/ is for mock-only tests.

Fix: Moved test file to tests/llm_responses_api_testing/ which is the
designated directory for integration tests that make real API calls.

Impact:
- Responses API requests now correctly set X-Initiator header
- Multi-turn agentic workflows (Plan mode, tool calls, agent flows) will
  only consume 1 premium request per session instead of 1 per turn
- Integration tests now in proper location per repository conventions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtstothard 2026-04-21 16:30:12 +01:00
parent 785f53f2ec
commit 3f79f15764
2 changed files with 40 additions and 37 deletions

View file

@ -88,6 +88,44 @@ class GithubCopilotResponsesAPIConfig(OpenAIResponsesAPIConfig):
"""
return dict(response_api_optional_params)
def transform_responses_api_request(
self,
model: str,
input: Union[str, ResponseInputParam],
response_api_optional_request_params: Dict,
litellm_params: GenericLiteLLMParams,
headers: dict,
) -> Dict:
"""
Transform the Responses API request and set X-Initiator header.
GitHub Copilot requires X-Initiator header based on input analysis.
This is set here (not in validate_environment) because only this method
has access to the input parameter.
"""
# Add X-Initiator header based on input analysis
initiator = self._get_initiator(input)
headers["X-Initiator"] = initiator
verbose_logger.debug(
f"GitHub Copilot Responses API: Set X-Initiator={initiator}"
)
# Add vision header if input contains images
if self._has_vision_input(input):
headers["copilot-vision-request"] = "true"
verbose_logger.debug(
"GitHub Copilot Responses API: Enabled vision request"
)
# Call parent to get request body (validates input, handles reasoning items)
return super().transform_responses_api_request(
model=model,
input=input,
response_api_optional_request_params=response_api_optional_request_params,
litellm_params=litellm_params,
headers=headers,
)
def validate_environment(
self,
headers: dict,
@ -139,23 +177,8 @@ class GithubCopilotResponsesAPIConfig(OpenAIResponsesAPIConfig):
# Merge with existing headers (user's extra_headers take priority)
merged_headers = {**default_headers, **headers}
# Analyze input to determine additional headers
input_param = self._get_input_from_params(litellm_params)
# Add X-Initiator header based on input analysis
if input_param is not None:
initiator = self._get_initiator(input_param)
merged_headers["X-Initiator"] = initiator
verbose_logger.debug(
f"GitHub Copilot Responses API: Set X-Initiator={initiator}"
)
# Add vision header if input contains images
if self._has_vision_input(input_param):
merged_headers["copilot-vision-request"] = "true"
verbose_logger.debug(
"GitHub Copilot Responses API: Enabled vision request"
)
# X-Initiator and vision headers are set in transform_responses_api_request
# where we have access to the input parameter
verbose_logger.debug(
f"GitHub Copilot Responses API: Successfully configured headers for model {model}"
@ -232,26 +255,6 @@ class GithubCopilotResponsesAPIConfig(OpenAIResponsesAPIConfig):
# ==================== Helper Methods ====================
def _get_input_from_params(
self, litellm_params: Optional[GenericLiteLLMParams]
) -> Optional[Union[str, ResponseInputParam]]:
"""
Extract input parameter from litellm_params.
The input parameter contains the conversation history and is needed
for vision detection and initiator determination.
"""
if litellm_params is None:
return None
# Try to get input from litellm_params
# This might be in different locations depending on how LiteLLM structures it
if hasattr(litellm_params, "input"):
return litellm_params.input
# If not found, return None and let the API handle it
return None
def _get_initiator(self, input_param: Union[str, ResponseInputParam]) -> str:
"""
Determine X-Initiator header value based on input analysis.