Spaces:
Sleeping
Sleeping
feat: sync proxy with upstream commits up to a4a0fb8 (model catalog refactor, desktop launcher, telemetry feature flags)
c817fe8 | """GitHub Models provider using OpenAI-compatible chat completions.""" | |
| from collections.abc import Mapping, Sequence | |
| from typing import Any | |
| import httpx | |
| from free_claude_code.application.model_metadata import ProviderModelInfo | |
| from free_claude_code.core.anthropic import ReasoningReplayMode | |
| from free_claude_code.providers.admission import ProviderAdmissionController | |
| from free_claude_code.providers.base import ProviderConfig | |
| from free_claude_code.providers.http import maybe_await_aclose | |
| from free_claude_code.providers.model_listing import ( | |
| ModelListResponseError, | |
| model_infos_from_ids, | |
| ) | |
| from free_claude_code.providers.openai_chat import ( | |
| NO_REASONING, | |
| OpenAIChatProfile, | |
| OpenAIChatProvider, | |
| OpenAIChatRequestPolicy, | |
| ) | |
| GITHUB_MODELS_CATALOG_URL = "https://models.github.ai/catalog/models" | |
| GITHUB_MODELS_API_VERSION = "2026-03-10" | |
| _REQUEST_POLICY = OpenAIChatRequestPolicy( | |
| provider_name="GITHUB_MODELS", | |
| reasoning_replay=ReasoningReplayMode.THINK_TAGS, | |
| ) | |
| _PROFILE = OpenAIChatProfile(_REQUEST_POLICY, NO_REASONING) | |
| _REQUIRED_MODEL_CAPABILITIES = frozenset({"streaming", "tool-calling"}) | |
| class GitHubModelsProvider(OpenAIChatProvider): | |
| """GitHub Models OpenAI-compatible inference provider.""" | |
| def __init__( | |
| self, config: ProviderConfig, *, admission: ProviderAdmissionController | |
| ): | |
| self._catalog_url = GITHUB_MODELS_CATALOG_URL | |
| self._model_list_client = httpx.AsyncClient( | |
| proxy=config.proxy or None, | |
| timeout=httpx.Timeout( | |
| config.http_read_timeout, | |
| connect=config.http_connect_timeout, | |
| read=config.http_read_timeout, | |
| write=config.http_write_timeout, | |
| ), | |
| ) | |
| super().__init__( | |
| config, | |
| profile=_PROFILE, | |
| admission=admission, | |
| default_headers=_github_models_default_headers(), | |
| ) | |
| async def cleanup(self) -> None: | |
| """Release provider client resources.""" | |
| await super().cleanup() | |
| await self._model_list_client.aclose() | |
| async def list_model_infos(self) -> frozenset[ProviderModelInfo]: | |
| """Return stream/tool-capable GitHub Models catalog ids.""" | |
| async def request() -> httpx.Response: | |
| response = await self._model_list_client.get( | |
| self._catalog_url, | |
| headers=self._model_list_headers(), | |
| ) | |
| try: | |
| response.raise_for_status() | |
| except Exception: | |
| await maybe_await_aclose(response) | |
| raise | |
| return response | |
| response = await self._admission.run_with_retry(request) | |
| try: | |
| try: | |
| payload = response.json() | |
| except ValueError as exc: | |
| raise ModelListResponseError( | |
| "GITHUB_MODELS model-list response is malformed: invalid JSON" | |
| ) from exc | |
| return model_infos_from_ids( | |
| _extract_supported_github_model_ids(payload), | |
| ) | |
| finally: | |
| await maybe_await_aclose(response) | |
| def _model_list_headers(self) -> dict[str, str]: | |
| return _github_models_api_headers(self._api_key) | |
| def _github_models_default_headers() -> dict[str, str]: | |
| return { | |
| "Accept": "application/vnd.github+json", | |
| "X-GitHub-Api-Version": GITHUB_MODELS_API_VERSION, | |
| } | |
| def _github_models_api_headers(api_key: str) -> dict[str, str]: | |
| return { | |
| **_github_models_default_headers(), | |
| "Authorization": f"Bearer {api_key}", | |
| } | |
| def _extract_supported_github_model_ids(payload: Any) -> frozenset[str]: | |
| """Extract stream/tool-capable model ids from GitHub's catalog array.""" | |
| if not _is_sequence(payload): | |
| raise ModelListResponseError( | |
| "GITHUB_MODELS model-list response is malformed: expected top-level array" | |
| ) | |
| model_ids: set[str] = set() | |
| for item in payload: | |
| if not isinstance(item, Mapping): | |
| raise ModelListResponseError( | |
| "GITHUB_MODELS model-list response is malformed: expected every item to be an object" | |
| ) | |
| model_id = item.get("id") | |
| if not isinstance(model_id, str) or not model_id.strip(): | |
| raise ModelListResponseError( | |
| "GITHUB_MODELS model-list response is malformed: expected every item to include id" | |
| ) | |
| capabilities = item.get("capabilities") | |
| if not _supports_streaming_tools(capabilities): | |
| continue | |
| model_ids.add(model_id) | |
| return frozenset(model_ids) | |
| def _supports_streaming_tools(capabilities: Any) -> bool: | |
| if not _is_sequence(capabilities): | |
| return False | |
| capability_names = {item for item in capabilities if isinstance(item, str)} | |
| return capability_names >= _REQUIRED_MODEL_CAPABILITIES | |
| def _is_sequence(value: Any) -> bool: | |
| return isinstance(value, Sequence) and not isinstance( | |
| value, str | bytes | bytearray | |
| ) | |