Created
April 21, 2026 09:10
-
-
Save minyk/4903d595b7d133f1276f86fd8dd34cae to your computer and use it in GitHub Desktop.
Another implementation for Nanobot GitHub copilot
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Index: nanobot/providers/github_copilot_provider.py | |
| IDEA additional info: | |
| Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP | |
| <+>UTF-8 | |
| =================================================================== | |
| diff --git a/nanobot/providers/github_copilot_provider.py b/nanobot/providers/github_copilot_provider.py | |
| --- a/nanobot/providers/github_copilot_provider.py (revision 6c24f24e9eadf1602858e96878c82887cd97fad3) | |
| +++ b/nanobot/providers/github_copilot_provider.py (date 1776762244731) | |
| @@ -11,6 +11,10 @@ | |
| from oauth_cli_kit.storage import FileTokenStorage | |
| from nanobot.providers.openai_compat_provider import OpenAICompatProvider | |
| +from nanobot.providers.openai_responses import ( | |
| + convert_messages, | |
| + convert_tools, | |
| +) | |
| DEFAULT_GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code" | |
| DEFAULT_GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token" | |
| @@ -26,7 +30,7 @@ | |
| EDITOR_PLUGIN_VERSION = "copilot-chat/0.26.0" | |
| _EXPIRY_SKEW_SECONDS = 60 | |
| _LONG_LIVED_TOKEN_SECONDS = 315360000 | |
| - | |
| +_RESPONSES_FAILURE_THRESHOLD = 3 | |
| def _storage() -> FileTokenStorage: | |
| return FileTokenStorage( | |
| @@ -154,6 +158,14 @@ | |
| _storage().save(token) | |
| return token | |
| +def _responses_circuit_key( | |
| + model: str | None, | |
| + default_model: str, | |
| + reasoning_effort: str | None, | |
| +) -> str: | |
| + model_name = (model or default_model).lower() | |
| + effort = reasoning_effort.lower() if isinstance(reasoning_effort, str) else "" | |
| + return f"{model_name}:{effort}" | |
| class GitHubCopilotProvider(OpenAICompatProvider): | |
| """Provider that exchanges a stored GitHub OAuth token for Copilot access tokens.""" | |
| @@ -175,6 +187,68 @@ | |
| spec=find_by_name("github_copilot"), | |
| ) | |
| + def _should_use_responses_api( | |
| + self, | |
| + model: str | None, | |
| + reasoning_effort: str | None, | |
| + ) -> bool: | |
| + model_name = (model or self.default_model).lower() | |
| + wants = False | |
| + if reasoning_effort and reasoning_effort.lower() != "none": | |
| + wants = True | |
| + elif any(token in model_name for token in ("gpt-5")): | |
| + wants = True | |
| + if not wants: | |
| + return False | |
| + | |
| + # Circuit breaker: skip after repeated failures, probe periodically. | |
| + key = _responses_circuit_key(model, self.default_model, reasoning_effort) | |
| + failures = self._responses_failures.get(key, 0) | |
| + if failures >= _RESPONSES_FAILURE_THRESHOLD: | |
| + tripped = self._responses_tripped_at.get(key, 0.0) | |
| + if (time.monotonic() - tripped) < _RESPONSES_PROBE_INTERVAL_S: | |
| + return False | |
| + # Half-open: allow one probe attempt | |
| + return True | |
| + | |
| + def _build_responses_body( | |
| + self, | |
| + messages: list[dict[str, Any]], | |
| + tools: list[dict[str, Any]] | None, | |
| + model: str | None, | |
| + max_tokens: int, | |
| + temperature: float, | |
| + reasoning_effort: str | None, | |
| + tool_choice: str | dict[str, Any] | None, | |
| + ) -> dict[str, Any]: | |
| + """Build a Responses API body for direct OpenAI requests.""" | |
| + model_name = model or self.default_model | |
| + model_name = model_name.split("/")[-1] | |
| + sanitized_messages = self._sanitize_messages(self._sanitize_empty_content(messages)) | |
| + instructions, input_items = convert_messages(sanitized_messages) | |
| + | |
| + body: dict[str, Any] = { | |
| + "model": model_name, | |
| + "instructions": instructions or None, | |
| + "input": input_items, | |
| + "max_output_tokens": max(1, max_tokens), | |
| + "store": False, | |
| + "stream": False, | |
| + } | |
| + | |
| + if self._supports_temperature(model_name, reasoning_effort): | |
| + body["temperature"] = temperature | |
| + | |
| + if reasoning_effort and reasoning_effort.lower() != "none": | |
| + body["reasoning"] = {"effort": reasoning_effort} | |
| + body["include"] = ["reasoning.encrypted_content"] | |
| + | |
| + if tools: | |
| + body["tools"] = convert_tools(tools) | |
| + body["tool_choice"] = tool_choice or "auto" | |
| + | |
| + return body | |
| + | |
| async def _get_copilot_access_token(self) -> str: | |
| now = time.time() | |
| if self._copilot_access_token and now < self._copilot_expires_at - _EXPIRY_SKEW_SECONDS: |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To use responses api with GitHub copilot subscription