mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-24 10:52:21 +00:00
fix(antigravity): bake in public OAuth client + default project fallback
Salvage follow-up on top of @pmos69's #29474. The PR resolved the Antigravity OAuth client purely by discovering it from an installed `agy` binary or HERMES_ANTIGRAVITY_CLIENT_ID/SECRET env vars, so users without agy installed hit a hard 'client ID not available' error. Antigravity's desktop OAuth client is a public, non-confidential installed-app client (PKCE provides the security), baked into every copy of the Antigravity CLI — same posture as the gemini-cli credentials Hermes already ships in google_oauth.py. Bake it in as the final fallback (env -> discovery -> public default) and add the public default Code Assist project as the discovery fallback, matching the reference Antigravity flow. Now consumers can authenticate directly without agy installed.
This commit is contained in:
parent
8baa4e9976
commit
b7a912ea45
3 changed files with 73 additions and 15 deletions
|
|
@ -146,10 +146,20 @@ def resolve_project_context(
|
|||
if env_project_id:
|
||||
return ProjectContext(project_id=env_project_id, source="env")
|
||||
info = load_code_assist(access_token)
|
||||
if info.project_id:
|
||||
return ProjectContext(
|
||||
project_id=info.project_id,
|
||||
managed_project_id=info.project_id,
|
||||
source="discovered",
|
||||
)
|
||||
# Discovery returned no project (common on fresh consumer accounts that
|
||||
# haven't been onboarded). Fall back to the public default project so the
|
||||
# call chain still succeeds — mirrors the Antigravity CLI reference flow.
|
||||
from agent.antigravity_oauth import DEFAULT_PROJECT_ID
|
||||
return ProjectContext(
|
||||
project_id=info.project_id,
|
||||
managed_project_id=info.project_id,
|
||||
source="discovered" if info.project_id else "unknown",
|
||||
project_id=DEFAULT_PROJECT_ID,
|
||||
managed_project_id=DEFAULT_PROJECT_ID,
|
||||
source="default",
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,26 @@ ENV_CLIENT_ID = "HERMES_ANTIGRAVITY_CLIENT_ID"
|
|||
ENV_CLIENT_SECRET = "HERMES_ANTIGRAVITY_CLIENT_SECRET"
|
||||
ENV_CLI_PATH = "HERMES_ANTIGRAVITY_CLI_PATH"
|
||||
|
||||
# Public Antigravity CLI desktop OAuth client. Like Google's gemini-cli
|
||||
# credentials (see agent/google_oauth.py), this is a DESKTOP OAuth client and
|
||||
# its "secret" is not confidential — installed-app clients have no
|
||||
# secret-keeping requirement (PKCE provides the security), and these creds are
|
||||
# baked into every copy of the Antigravity CLI. Shipping them as a fallback
|
||||
# lets users without `agy` installed authenticate directly. Split into parts
|
||||
# with explicit comments per the convention in google_oauth.py.
|
||||
_PUBLIC_CLIENT_ID_PROJECT_NUM = "1071006060591"
|
||||
_PUBLIC_CLIENT_ID_HASH = "tmhssin2h21lcre235vtolojh4g403ep"
|
||||
_PUBLIC_CLIENT_SECRET_SUFFIX = "K58FWR486LdLJ1mLB8sXC4z6qDAf"
|
||||
|
||||
_DEFAULT_CLIENT_ID = (
|
||||
f"{_PUBLIC_CLIENT_ID_PROJECT_NUM}-{_PUBLIC_CLIENT_ID_HASH}"
|
||||
".apps.googleusercontent.com"
|
||||
)
|
||||
_DEFAULT_CLIENT_SECRET = f"GOCSPX-{_PUBLIC_CLIENT_SECRET_SUFFIX}"
|
||||
|
||||
# Fallback project ID when Code Assist project discovery fails entirely.
|
||||
DEFAULT_PROJECT_ID = "rising-fact-p41fc"
|
||||
|
||||
_CLIENT_ID_PATTERN = re.compile(
|
||||
r"([0-9]{8,}-[a-z0-9]{20,}\.apps\.googleusercontent\.com)"
|
||||
)
|
||||
|
|
@ -335,7 +355,9 @@ def _get_client_id() -> str:
|
|||
if env_val:
|
||||
return env_val
|
||||
discovered, _ = _discover_client_credentials()
|
||||
return discovered
|
||||
if discovered:
|
||||
return discovered
|
||||
return _DEFAULT_CLIENT_ID
|
||||
|
||||
|
||||
def _get_client_secret() -> str:
|
||||
|
|
@ -343,7 +365,9 @@ def _get_client_secret() -> str:
|
|||
if env_val:
|
||||
return env_val
|
||||
_, discovered = _discover_client_credentials()
|
||||
return discovered
|
||||
if discovered:
|
||||
return discovered
|
||||
return _DEFAULT_CLIENT_SECRET
|
||||
|
||||
|
||||
def _iter_client_credential_candidates() -> list[Tuple[str, str]]:
|
||||
|
|
@ -354,15 +378,26 @@ def _iter_client_credential_candidates() -> list[Tuple[str, str]]:
|
|||
|
||||
_discover_client_credentials()
|
||||
cached = _discovered_creds_cache.get("candidates")
|
||||
candidates: list[Tuple[str, str]] = []
|
||||
if isinstance(cached, list):
|
||||
return [
|
||||
candidates = [
|
||||
(str(client_id), str(client_secret))
|
||||
for client_id, client_secret in cached
|
||||
if client_id and client_secret
|
||||
]
|
||||
client_id = str(_discovered_creds_cache.get("client_id") or "")
|
||||
client_secret = str(_discovered_creds_cache.get("client_secret") or "")
|
||||
return [(client_id, client_secret)] if client_id and client_secret else []
|
||||
else:
|
||||
client_id = str(_discovered_creds_cache.get("client_id") or "")
|
||||
client_secret = str(_discovered_creds_cache.get("client_secret") or "")
|
||||
if client_id and client_secret:
|
||||
candidates = [(client_id, client_secret)]
|
||||
|
||||
# Always include the public baked-in default as a last-resort candidate so
|
||||
# users without `agy` installed can still authenticate. De-dupe in case
|
||||
# discovery already surfaced the same client.
|
||||
default_pair = (_DEFAULT_CLIENT_ID, _DEFAULT_CLIENT_SECRET)
|
||||
if default_pair not in candidates:
|
||||
candidates.append(default_pair)
|
||||
return candidates
|
||||
|
||||
|
||||
def _require_client_id() -> str:
|
||||
|
|
|
|||
|
|
@ -102,13 +102,26 @@ class TestAntigravityCredentials:
|
|||
assert antigravity_oauth._get_client_id().startswith("1071006060591-")
|
||||
assert antigravity_oauth._get_client_secret() == fake_client_secret
|
||||
|
||||
def test_missing_client_credentials_raise_with_setup_hint(self):
|
||||
from agent.antigravity_oauth import AntigravityOAuthError, _require_client_id
|
||||
def test_missing_discovery_falls_back_to_public_default(self, monkeypatch):
|
||||
# With no env override and no discoverable agy install, the public
|
||||
# baked-in Antigravity desktop OAuth client is used as the floor so
|
||||
# users without `agy` installed can still authenticate (PKCE makes the
|
||||
# installed-app "secret" non-confidential, same as gemini-cli).
|
||||
from agent import antigravity_oauth
|
||||
from agent.antigravity_oauth import (
|
||||
_DEFAULT_CLIENT_ID,
|
||||
_DEFAULT_CLIENT_SECRET,
|
||||
_require_client_id,
|
||||
)
|
||||
|
||||
with pytest.raises(AntigravityOAuthError) as exc_info:
|
||||
_require_client_id()
|
||||
assert exc_info.value.code == "antigravity_oauth_client_id_missing"
|
||||
assert "HERMES_ANTIGRAVITY_CLI_PATH" in str(exc_info.value)
|
||||
monkeypatch.delenv("HERMES_ANTIGRAVITY_CLIENT_ID", raising=False)
|
||||
monkeypatch.delenv("HERMES_ANTIGRAVITY_CLIENT_SECRET", raising=False)
|
||||
monkeypatch.delenv("HERMES_ANTIGRAVITY_CLI_PATH", raising=False)
|
||||
antigravity_oauth._discovered_creds_cache.clear()
|
||||
|
||||
assert _require_client_id() == _DEFAULT_CLIENT_ID
|
||||
assert antigravity_oauth._get_client_secret() == _DEFAULT_CLIENT_SECRET
|
||||
assert _DEFAULT_CLIENT_ID.startswith("1071006060591-")
|
||||
|
||||
def test_pkce_challenge_is_s256(self):
|
||||
import base64
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue