Skip to content

Instantly share code, notes, and snippets.

@minyk
Created April 21, 2026 09:10
Show Gist options
  • Select an option

  • Save minyk/4903d595b7d133f1276f86fd8dd34cae to your computer and use it in GitHub Desktop.

Select an option

Save minyk/4903d595b7d133f1276f86fd8dd34cae to your computer and use it in GitHub Desktop.
Another implementation for Nanobot GitHub copilot
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:
@minyk

minyk commented Apr 21, 2026

Copy link
Copy Markdown
Author

To use responses api with GitHub copilot subscription

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment