diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index 1e3d39c7ba..65641a5fbb 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -2862,10 +2862,11 @@ def auxiliary_max_tokens_param(value: int) -> dict: """ custom_base = _current_custom_base_url() or_key = os.getenv("OPENROUTER_API_KEY") - # Only use max_completion_tokens for direct OpenAI custom endpoints + # Use max_completion_tokens for direct OpenAI-compatible providers that reject + # max_tokens on newer GPT-4o/o-series/GPT-5-style models. if (not or_key and _read_nous_auth() is None - and base_url_hostname(custom_base) == "api.openai.com"): + and base_url_hostname(custom_base) in {"api.openai.com", "api.githubcopilot.com"}): return {"max_completion_tokens": value} return {"max_tokens": value} diff --git a/run_agent.py b/run_agent.py index b3a7003e77..3e1f2772a9 100644 --- a/run_agent.py +++ b/run_agent.py @@ -2852,6 +2852,16 @@ class AIAgent: url = getattr(self, "_base_url_lower", "") or "" return "openai.azure.com" in url + def _is_github_copilot_url(self, base_url: str = None) -> bool: + """Return True when a base URL targets GitHub Copilot's OpenAI-compatible API.""" + if base_url is not None: + hostname = base_url_hostname(base_url) + else: + hostname = getattr(self, "_base_url_hostname", "") or base_url_hostname( + getattr(self, "_base_url_lower", "") + ) + return hostname == "api.githubcopilot.com" + def _resolved_api_call_timeout(self) -> float: """Resolve the effective per-call request timeout in seconds. @@ -3047,7 +3057,7 @@ class AIAgent: OpenAI-compatible endpoint. OpenRouter, local models, and older OpenAI models use 'max_tokens'. """ - if self._is_direct_openai_url() or self._is_azure_openai_url(): + if self._is_direct_openai_url() or self._is_azure_openai_url() or self._is_github_copilot_url(): return {"max_completion_tokens": value} return {"max_tokens": value} diff --git a/tests/agent/test_auxiliary_client.py b/tests/agent/test_auxiliary_client.py index 55a7e969e1..16e563a91a 100644 --- a/tests/agent/test_auxiliary_client.py +++ b/tests/agent/test_auxiliary_client.py @@ -57,6 +57,18 @@ def codex_auth_dir(tmp_path, monkeypatch): return codex_dir +class TestAuxiliaryMaxTokensParam: + def test_uses_max_completion_tokens_for_github_copilot_custom_base(self): + with patch("agent.auxiliary_client._resolve_custom_runtime", return_value=("https://api.githubcopilot.com", "key", None)), \ + patch("agent.auxiliary_client._read_nous_auth", return_value=None): + assert auxiliary_max_tokens_param(2048) == {"max_completion_tokens": 2048} + + def test_uses_max_completion_tokens_for_github_copilot_custom_base_path(self): + with patch("agent.auxiliary_client._resolve_custom_runtime", return_value=("https://api.githubcopilot.com/chat/completions", "key", None)), \ + patch("agent.auxiliary_client._read_nous_auth", return_value=None): + assert auxiliary_max_tokens_param(2048) == {"max_completion_tokens": 2048} + + class TestNormalizeAuxProvider: def test_maps_github_copilot_aliases(self): assert _normalize_aux_provider("github") == "copilot" diff --git a/tests/run_agent/test_run_agent.py b/tests/run_agent/test_run_agent.py index 42f1902db8..cbce772d3a 100644 --- a/tests/run_agent/test_run_agent.py +++ b/tests/run_agent/test_run_agent.py @@ -3666,6 +3666,18 @@ class TestMaxTokensParam: result = agent._max_tokens_param(4096) assert result == {"max_completion_tokens": 4096} + def test_returns_max_completion_tokens_for_github_copilot(self, agent): + """GitHub Copilot's OpenAI-compatible API rejects max_tokens for newer models.""" + agent.base_url = "https://api.githubcopilot.com" + result = agent._max_tokens_param(4096) + assert result == {"max_completion_tokens": 4096} + + def test_returns_max_completion_tokens_for_github_copilot_path(self, agent): + """Detect Copilot by hostname even when the configured URL includes a path.""" + agent.base_url = "https://api.githubcopilot.com/chat/completions" + result = agent._max_tokens_param(4096) + assert result == {"max_completion_tokens": 4096} + class TestAzureOpenAIRouting: """Verify Azure OpenAI endpoints stay on chat_completions for gpt-5.x."""