fix(providers): make GitHub Copilot backend work with GPT-5/o-series models
Calling GitHub Copilot with `gpt-5.*` / `o*` models (e.g.
`github_copilot/gpt-5.4`, `github_copilot/gpt-5.4-mini`) failed with a
chain of misleading errors:
1. `Unsupported parameter: 'max_tokens' is not supported with this
model. Use 'max_completion_tokens' instead.`
2. `model "gpt-5.4-mini" is not accessible via the /chat/completions
endpoint` (`unsupported_api_for_model`).
3. `The requested model is not supported.` (`model_not_supported`)
even after routing to /responses.
Root causes (each one masked the next):
* The `github_copilot` ProviderSpec did not opt into
`supports_max_completion_tokens`, so `_build_kwargs` always sent the
legacy `max_tokens` parameter that GPT-5/o-series reject.
* `_should_use_responses_api` was hard-gated to
`spec.name == "openai"` plus a direct-OpenAI base URL, so the
GitHub Copilot backend always went through /chat/completions even
for models the Copilot gateway exposes only via /responses
(e.g. `gpt-5.4-mini`).
* When /responses did fail on github_copilot, the existing
"compatibility marker" heuristic silently fell back to
/chat/completions — which can never succeed for these models — so
the real upstream error was hidden.
* `_build_responses_body` did not honour `spec.strip_model_prefix`,
so the request body sent `model="github_copilot/gpt-5.4-mini"`
(with the routing prefix), which the Copilot gateway rejects with
`model_not_supported`. (`_build_kwargs` already stripped it; this
branch was missed.)
Fix:
* registry.py: set `supports_max_completion_tokens=True` on the
`github_copilot` spec so requests use `max_completion_tokens`.
* openai_compat_provider.py:
- `_should_use_responses_api` now also allows the
`github_copilot` spec, and skips the direct-OpenAI base check
for it (the Copilot gateway is its own base URL).
- `_build_responses_body` now strips the model routing prefix
when `spec.strip_model_prefix` is set, matching `_build_kwargs`.
- `chat` / `chat_stream` no longer fall back from /responses to
/chat/completions on the `github_copilot` spec: the fallback
cannot succeed for GPT-5/o-series and would mask the real
gateway error.
Tests:
* tests/cli/test_commands.py: switched the
`test_github_copilot_provider_refreshes_client_api_key_before_chat`
fixture model from `gpt-5.1` to `gpt-4` so it continues to exercise
the /chat/completions code path it was designed for (gpt-5.1 now
correctly routes to /responses on github_copilot).
* `pytest tests/providers/ tests/cli/test_commands.py` — 314 passed.
* Verified end-to-end against the live Copilot gateway with both
`github_copilot/gpt-5.4` and `github_copilot/gpt-5.4-mini`.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -446,10 +446,11 @@ class OpenAICompatProvider(LLMProvider):
|
||||
reasoning_effort: str | None,
|
||||
) -> bool:
|
||||
"""Use Responses API only for direct OpenAI requests that benefit from it."""
|
||||
if self._spec and self._spec.name != "openai":
|
||||
return False
|
||||
if not _is_direct_openai_base(self._effective_base):
|
||||
if self._spec and self._spec.name not in ("openai", "github_copilot"):
|
||||
return False
|
||||
if self._spec is None or self._spec.name != "github_copilot":
|
||||
if not _is_direct_openai_base(self._effective_base):
|
||||
return False
|
||||
|
||||
model_name = (model or self.default_model).lower()
|
||||
wants = False
|
||||
@@ -527,6 +528,8 @@ class OpenAICompatProvider(LLMProvider):
|
||||
) -> dict[str, Any]:
|
||||
"""Build a Responses API body for direct OpenAI requests."""
|
||||
model_name = model or self.default_model
|
||||
if self._spec and self._spec.strip_model_prefix:
|
||||
model_name = model_name.split("/")[-1]
|
||||
sanitized_messages = self._sanitize_messages(self._sanitize_empty_content(messages))
|
||||
instructions, input_items = convert_messages(sanitized_messages)
|
||||
|
||||
@@ -987,6 +990,11 @@ class OpenAICompatProvider(LLMProvider):
|
||||
self._record_responses_success(model, reasoning_effort)
|
||||
return result
|
||||
except Exception as responses_error:
|
||||
if self._spec and self._spec.name == "github_copilot":
|
||||
# Copilot gateway exposes GPT-5/o-series only via /responses;
|
||||
# falling back to /chat/completions cannot succeed and would
|
||||
# hide the real error.
|
||||
raise
|
||||
if not self._should_fallback_from_responses_error(responses_error):
|
||||
raise
|
||||
self._record_responses_failure(model, reasoning_effort)
|
||||
@@ -1045,6 +1053,11 @@ class OpenAICompatProvider(LLMProvider):
|
||||
reasoning_content=reasoning_content,
|
||||
)
|
||||
except Exception as responses_error:
|
||||
if self._spec and self._spec.name == "github_copilot":
|
||||
# Copilot gateway exposes GPT-5/o-series only via /responses;
|
||||
# falling back to /chat/completions cannot succeed and would
|
||||
# hide the real error.
|
||||
raise
|
||||
if not self._should_fallback_from_responses_error(responses_error):
|
||||
raise
|
||||
self._record_responses_failure(model, reasoning_effort)
|
||||
|
||||
@@ -223,6 +223,7 @@ PROVIDERS: tuple[ProviderSpec, ...] = (
|
||||
default_api_base="https://api.githubcopilot.com",
|
||||
strip_model_prefix=True,
|
||||
is_oauth=True,
|
||||
supports_max_completion_tokens=True,
|
||||
),
|
||||
# DeepSeek: OpenAI-compatible at api.deepseek.com
|
||||
ProviderSpec(
|
||||
|
||||
@@ -421,13 +421,13 @@ async def test_github_copilot_provider_refreshes_client_api_key_before_chat():
|
||||
})
|
||||
|
||||
with patch("nanobot.providers.openai_compat_provider.AsyncOpenAI", return_value=mock_client):
|
||||
provider = GitHubCopilotProvider(default_model="github-copilot/gpt-5.1")
|
||||
provider = GitHubCopilotProvider(default_model="github-copilot/gpt-4")
|
||||
|
||||
provider._get_copilot_access_token = AsyncMock(return_value="copilot-access-token")
|
||||
|
||||
response = await provider.chat(
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
model="github-copilot/gpt-5.1",
|
||||
model="github-copilot/gpt-4",
|
||||
max_tokens=16,
|
||||
temperature=0.1,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user