mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-04 08:28:36 +00:00
feat(providers): allow GitHub Copilot endpoint overrides for enterprise/GHE (#4220)
This commit is contained in:
parent
82ffce1474
commit
4beca25ceb
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
import time
|
import time
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
@ -29,6 +30,12 @@ _EXPIRY_SKEW_SECONDS = 60
|
|||||||
_LONG_LIVED_TOKEN_SECONDS = 315360000
|
_LONG_LIVED_TOKEN_SECONDS = 315360000
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve(env_var: str, default: str) -> str:
|
||||||
|
"""Allow GitHub Enterprise / Copilot for Business deployments to override defaults via env."""
|
||||||
|
value = os.environ.get(env_var)
|
||||||
|
return value.strip() if value and value.strip() else default
|
||||||
|
|
||||||
|
|
||||||
def get_storage() -> FileTokenStorage:
|
def get_storage() -> FileTokenStorage:
|
||||||
return FileTokenStorage(
|
return FileTokenStorage(
|
||||||
token_filename=TOKEN_FILENAME,
|
token_filename=TOKEN_FILENAME,
|
||||||
@ -68,11 +75,16 @@ def login_github_copilot(
|
|||||||
printer = print_fn or print
|
printer = print_fn or print
|
||||||
timeout = httpx.Timeout(20.0, connect=20.0)
|
timeout = httpx.Timeout(20.0, connect=20.0)
|
||||||
|
|
||||||
|
client_id = _resolve("NANOBOT_GITHUB_COPILOT_CLIENT_ID", GITHUB_COPILOT_CLIENT_ID)
|
||||||
|
device_code_url = _resolve("NANOBOT_GITHUB_DEVICE_CODE_URL", DEFAULT_GITHUB_DEVICE_CODE_URL)
|
||||||
|
access_token_url = _resolve("NANOBOT_GITHUB_ACCESS_TOKEN_URL", DEFAULT_GITHUB_ACCESS_TOKEN_URL)
|
||||||
|
user_url = _resolve("NANOBOT_GITHUB_USER_URL", DEFAULT_GITHUB_USER_URL)
|
||||||
|
|
||||||
with httpx.Client(timeout=timeout, follow_redirects=True, trust_env=True) as client:
|
with httpx.Client(timeout=timeout, follow_redirects=True, trust_env=True) as client:
|
||||||
response = client.post(
|
response = client.post(
|
||||||
DEFAULT_GITHUB_DEVICE_CODE_URL,
|
device_code_url,
|
||||||
headers={"Accept": "application/json", "User-Agent": USER_AGENT},
|
headers={"Accept": "application/json", "User-Agent": USER_AGENT},
|
||||||
data={"client_id": GITHUB_COPILOT_CLIENT_ID, "scope": GITHUB_COPILOT_SCOPE},
|
data={"client_id": client_id, "scope": GITHUB_COPILOT_SCOPE},
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
payload = response.json()
|
payload = response.json()
|
||||||
@ -96,10 +108,10 @@ def login_github_copilot(
|
|||||||
token_expires_in = _LONG_LIVED_TOKEN_SECONDS
|
token_expires_in = _LONG_LIVED_TOKEN_SECONDS
|
||||||
while time.time() < deadline:
|
while time.time() < deadline:
|
||||||
poll = client.post(
|
poll = client.post(
|
||||||
DEFAULT_GITHUB_ACCESS_TOKEN_URL,
|
access_token_url,
|
||||||
headers={"Accept": "application/json", "User-Agent": USER_AGENT},
|
headers={"Accept": "application/json", "User-Agent": USER_AGENT},
|
||||||
data={
|
data={
|
||||||
"client_id": GITHUB_COPILOT_CLIENT_ID,
|
"client_id": client_id,
|
||||||
"device_code": device_code,
|
"device_code": device_code,
|
||||||
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
|
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
|
||||||
},
|
},
|
||||||
@ -132,7 +144,7 @@ def login_github_copilot(
|
|||||||
raise RuntimeError("GitHub device flow timed out.")
|
raise RuntimeError("GitHub device flow timed out.")
|
||||||
|
|
||||||
user = client.get(
|
user = client.get(
|
||||||
DEFAULT_GITHUB_USER_URL,
|
user_url,
|
||||||
headers={
|
headers={
|
||||||
"Authorization": f"Bearer {access_token}",
|
"Authorization": f"Bearer {access_token}",
|
||||||
"Accept": "application/vnd.github+json",
|
"Accept": "application/vnd.github+json",
|
||||||
@ -164,7 +176,7 @@ class GitHubCopilotProvider(OpenAICompatProvider):
|
|||||||
self._copilot_expires_at: float = 0.0
|
self._copilot_expires_at: float = 0.0
|
||||||
super().__init__(
|
super().__init__(
|
||||||
api_key="no-key",
|
api_key="no-key",
|
||||||
api_base=DEFAULT_COPILOT_BASE_URL,
|
api_base=_resolve("NANOBOT_COPILOT_BASE_URL", DEFAULT_COPILOT_BASE_URL),
|
||||||
default_model=default_model,
|
default_model=default_model,
|
||||||
extra_headers={
|
extra_headers={
|
||||||
"Editor-Version": EDITOR_VERSION,
|
"Editor-Version": EDITOR_VERSION,
|
||||||
@ -186,7 +198,7 @@ class GitHubCopilotProvider(OpenAICompatProvider):
|
|||||||
timeout = httpx.Timeout(20.0, connect=20.0)
|
timeout = httpx.Timeout(20.0, connect=20.0)
|
||||||
async with httpx.AsyncClient(timeout=timeout, follow_redirects=True, trust_env=True) as client:
|
async with httpx.AsyncClient(timeout=timeout, follow_redirects=True, trust_env=True) as client:
|
||||||
response = await client.get(
|
response = await client.get(
|
||||||
DEFAULT_COPILOT_TOKEN_URL,
|
_resolve("NANOBOT_COPILOT_TOKEN_URL", DEFAULT_COPILOT_TOKEN_URL),
|
||||||
headers=_copilot_headers(github_token.access),
|
headers=_copilot_headers(github_token.access),
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|||||||
32
tests/providers/test_github_copilot_enterprise.py
Normal file
32
tests/providers/test_github_copilot_enterprise.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
"""Regression tests for GitHub Enterprise / Copilot for Business endpoint overrides (#4220)."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from nanobot.providers import github_copilot_provider as gc
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_falls_back_to_default_without_env(monkeypatch):
|
||||||
|
monkeypatch.delenv("NANOBOT_COPILOT_BASE_URL", raising=False)
|
||||||
|
assert gc._resolve("NANOBOT_COPILOT_BASE_URL", gc.DEFAULT_COPILOT_BASE_URL) == (
|
||||||
|
gc.DEFAULT_COPILOT_BASE_URL
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_uses_env_override_and_strips(monkeypatch):
|
||||||
|
monkeypatch.setenv("NANOBOT_COPILOT_TOKEN_URL", " https://api.acme.ghe.com/copilot_internal/v2/token ")
|
||||||
|
assert gc._resolve("NANOBOT_COPILOT_TOKEN_URL", gc.DEFAULT_COPILOT_TOKEN_URL) == (
|
||||||
|
"https://api.acme.ghe.com/copilot_internal/v2/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_blank_env_override_falls_back_to_default(monkeypatch):
|
||||||
|
monkeypatch.setenv("NANOBOT_COPILOT_BASE_URL", " ")
|
||||||
|
assert gc._resolve("NANOBOT_COPILOT_BASE_URL", gc.DEFAULT_COPILOT_BASE_URL) == (
|
||||||
|
gc.DEFAULT_COPILOT_BASE_URL
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_provider_api_base_honors_env_override(monkeypatch):
|
||||||
|
monkeypatch.setenv("NANOBOT_COPILOT_BASE_URL", "https://copilot-api.acme.ghe.com")
|
||||||
|
provider = gc.GitHubCopilotProvider()
|
||||||
|
assert provider.api_base == "https://copilot-api.acme.ghe.com"
|
||||||
Loading…
x
Reference in New Issue
Block a user