Generic plugin-OAuth support that the Solstice provider needs. None of it names a provider: - OAuthPKCEConfig.token_request: a confidential client whose secret lives behind a broker hands the grant to the plugin in place of a token_url POST. The old refresh_token is kept when the response omits one, which Google does on refresh. - Plugin OAuth rows rotate 2 minutes ahead of their stored expires_at_ms (shared with the anthropic rule). Opaque bearers such as Google's ya29.* carry no JWT exp, so this stored expiry is the only clock they have. - A row whose refresh grant just went DEAD stays DEAD on the inference 401 that follows; before this it was re-benched EXHAUSTED and the dead token was replayed every cooldown. - The auxiliary client serves an OAuth plugin that ships create_client from its pooled row on the main runtime's endpoint (compression, titles). It used to return "not directly supported". - A 401 on a plugin OAuth row prints a re-sign-in hint, not the API-key advice. - The Gemini native adapter exposes its method names, auth headers and error builder so a Bearer-auth subclass can reuse it without the API-key remedies.
46 lines
2.4 KiB
Python
46 lines
2.4 KiB
Python
"""Auxiliary client for OAuth-shaped model-provider plugins (``auth_type`` oauth_external / oauth_device_code).
|
|
|
|
Built-in OAuth routes (nous, openai-codex, xai-oauth) have their own branches in ``auxiliary_client``.
|
|
A plugin OAuth provider reaches this one: its credential is the pooled row ``hermes auth add <name>``
|
|
wrote, and its transport comes from ``ProviderProfile.create_client``. ``CredentialPool.select()``
|
|
rotates an expiring row before leasing it, so compression and titling never send a dead bearer.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
|
|
def _configured_endpoint(provider: str) -> str:
|
|
"""The endpoint the main runtime would use: ``model.base_url`` when ``model.provider`` is this
|
|
provider (a relay / proxy override), else the registered profile's own."""
|
|
from hermes_cli.auth import PROVIDER_REGISTRY
|
|
from hermes_cli.runtime_provider import _config_base_url_for_provider, _get_model_config
|
|
pconfig = PROVIDER_REGISTRY.get(provider)
|
|
return _config_base_url_for_provider(_get_model_config(), provider) or (pconfig.inference_base_url if pconfig else "")
|
|
|
|
|
|
def resolve_plugin_oauth_client(req: Any) -> tuple[Any, Any]:
|
|
"""``(client, model)`` for ``req.provider``, or ``(None, None)`` when signed out or transport-less.
|
|
|
|
An explicit key from the main runtime wins, so aux shares the session's bearer.
|
|
"""
|
|
from agent import auxiliary_client as aux
|
|
|
|
provider = req.provider
|
|
api_key = aux._normalize_api_key(req.explicit_api_key)
|
|
entry = None
|
|
if not api_key:
|
|
_exists, entry = aux._select_pool_entry(provider)
|
|
api_key = str(getattr(entry, "runtime_api_key", "") or "") if entry is not None else ""
|
|
base_url = (req.explicit_base_url or str(getattr(entry, "runtime_base_url", "") or "")
|
|
or _configured_endpoint(provider)).strip().rstrip("/")
|
|
client = aux._api_key_profile_supplied_client(provider, api_key=api_key, base_url=base_url) if api_key else None
|
|
if client is None:
|
|
aux._log_once_debug(aux._LOGGED_UNSUPPORTED_OAUTH_KEYS, provider,
|
|
"resolve_provider_client: OAuth provider %s has no signed-in credential or "
|
|
"plugin transport, try 'auto'", provider)
|
|
return None, None
|
|
model = aux._normalize_resolved_model(req.model or aux._get_aux_model_for_provider(provider), provider)
|
|
return aux._route_client(req, client, model)
|