mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-01 12:02:05 +00:00
fix(copilot): prefer endpoints.api for base URL, guard empty chat base URL
Folds @trevorgordon981's #50590 into difujia's #15139: - exchange_copilot_token now prefers the authoritative endpoints.api from the token-exchange response, falling back to the proxy-ep-derived host - resolve_api_key_provider_credentials gains a copilot branch that resolves the account-specific base URL and a non-empty last-resort guard, so chat inference never wedges on an empty base URL (#50252) Co-authored-by: Trevor Gordon <trevorbgordon@gmail.com>
This commit is contained in:
parent
fb07215844
commit
15e44527ab
2 changed files with 47 additions and 8 deletions
|
|
@ -6357,6 +6357,26 @@ def resolve_api_key_provider_credentials(provider_id: str) -> Dict[str, Any]:
|
|||
base_url = _resolve_kimi_base_url(api_key, pconfig.inference_base_url, env_url)
|
||||
elif provider_id == "zai":
|
||||
base_url = _resolve_zai_base_url(api_key, pconfig.inference_base_url, env_url)
|
||||
elif provider_id == "copilot":
|
||||
# Resolve the Copilot API base URL from the token-exchange response
|
||||
# (endpoints.api, with a proxy-ep fallback), which is authoritative
|
||||
# for Enterprise / proxied accounts. Falls back to the registry
|
||||
# default and is guarded non-empty below so chat inference never
|
||||
# resolves an empty base URL (#50252).
|
||||
base_url = env_url.rstrip("/") if env_url else pconfig.inference_base_url
|
||||
try:
|
||||
from hermes_cli.copilot_auth import (
|
||||
resolve_copilot_token,
|
||||
get_copilot_api_token,
|
||||
)
|
||||
raw_token, _ = resolve_copilot_token()
|
||||
if raw_token:
|
||||
_, resolved = get_copilot_api_token(raw_token)
|
||||
resolved = (resolved or "").strip()
|
||||
if resolved:
|
||||
base_url = resolved
|
||||
except Exception as exc:
|
||||
logger.debug("Copilot base URL resolution fell back to default: %s", exc)
|
||||
elif env_url:
|
||||
base_url = env_url.rstrip("/")
|
||||
else:
|
||||
|
|
@ -6365,6 +6385,12 @@ def resolve_api_key_provider_credentials(provider_id: str) -> Dict[str, Any]:
|
|||
if provider_id == "lmstudio":
|
||||
base_url = _normalize_lmstudio_runtime_base_url(base_url)
|
||||
|
||||
# Last-resort guard: an API-key provider must never hand back an empty
|
||||
# base URL (a set-but-empty COPILOT_API_BASE_URL or similar env override
|
||||
# otherwise wedges chat inference — #50252).
|
||||
if not (isinstance(base_url, str) and base_url.strip()):
|
||||
base_url = pconfig.inference_base_url
|
||||
|
||||
return {
|
||||
"provider": provider_id,
|
||||
"api_key": api_key,
|
||||
|
|
|
|||
|
|
@ -312,8 +312,10 @@ def exchange_copilot_token(raw_token: str, *, timeout: float = 10.0) -> tuple[st
|
|||
|
||||
The returned token is a semicolon-separated string (not a standard JWT)
|
||||
used as ``Authorization: Bearer <token>`` for Copilot API requests.
|
||||
If the token contains a ``proxy-ep`` field (enterprise accounts), the
|
||||
derived API base URL is returned; otherwise ``base_url`` is None.
|
||||
``base_url`` is the account-specific API host: the authoritative
|
||||
``endpoints.api`` advertised by the exchange (enterprise/proxied
|
||||
accounts), falling back to a host derived from the token's ``proxy-ep``
|
||||
field. Individual accounts have neither, so ``base_url`` is None.
|
||||
|
||||
Results are cached in-process and reused until close to expiry.
|
||||
Raises ``ValueError`` on failure.
|
||||
|
|
@ -354,10 +356,20 @@ def exchange_copilot_token(raw_token: str, *, timeout: float = 10.0) -> tuple[st
|
|||
# Convert expires_at to float if needed
|
||||
expires_at = float(expires_at) if expires_at else time.time() + 1800
|
||||
|
||||
# Derive enterprise base URL from proxy-ep in the token.
|
||||
# The token is semicolon-separated: "tid=xxx;exp=xxx;proxy-ep=proxy.enterprise.githubcopilot.com;..."
|
||||
# Replace leading "proxy." with "api." to get the API base URL.
|
||||
base_url = _derive_base_url_from_proxy_ep(api_token)
|
||||
# Resolve the account-specific API base URL. GitHub advertises the
|
||||
# authoritative endpoint under ``endpoints.api`` in the exchange response
|
||||
# (it differs for Copilot Enterprise / proxied accounts). When the
|
||||
# response omits it, fall back to deriving the host from the ``proxy-ep``
|
||||
# field embedded in the exchanged token. Individual accounts have neither,
|
||||
# so ``base_url`` stays None and callers use the registry default.
|
||||
base_url: Optional[str] = None
|
||||
endpoints = data.get("endpoints")
|
||||
if isinstance(endpoints, dict):
|
||||
api_endpoint = str(endpoints.get("api") or "").strip().rstrip("/")
|
||||
if api_endpoint:
|
||||
base_url = api_endpoint
|
||||
if not base_url:
|
||||
base_url = _derive_base_url_from_proxy_ep(api_token)
|
||||
|
||||
_jwt_cache[fp] = (api_token, expires_at, base_url)
|
||||
logger.debug(
|
||||
|
|
@ -408,8 +420,9 @@ def get_copilot_api_token(raw_token: str) -> tuple[str, Optional[str]]:
|
|||
account type). This preserves existing behaviour for accounts that don't
|
||||
need exchange while enabling access to internal-only models for those that do.
|
||||
|
||||
``base_url`` is the enterprise API endpoint derived from the token's
|
||||
``proxy-ep`` field, or None for individual accounts.
|
||||
``base_url`` is the account-specific API endpoint advertised by the
|
||||
exchange (``endpoints.api``, with a ``proxy-ep`` fallback), or None for
|
||||
individual accounts.
|
||||
"""
|
||||
if not raw_token:
|
||||
return raw_token, None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue