diff --git a/nanobot/providers/openai_compat_provider.py b/nanobot/providers/openai_compat_provider.py index 4f726df2..d9eb64dd 100644 --- a/nanobot/providers/openai_compat_provider.py +++ b/nanobot/providers/openai_compat_provider.py @@ -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) diff --git a/nanobot/providers/registry.py b/nanobot/providers/registry.py index 05237338..f633cc83 100644 --- a/nanobot/providers/registry.py +++ b/nanobot/providers/registry.py @@ -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( diff --git a/tests/cli/test_commands.py b/tests/cli/test_commands.py index 0344af23..2719beed 100644 --- a/tests/cli/test_commands.py +++ b/tests/cli/test_commands.py @@ -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, )