fix(gemini): route Vertex express keys to aiplatform in doctor and dashboard key probes
hermes doctor and the dashboard GEMINI_API_KEY validator still sent AQ. express keys to generativelanguage.googleapis.com, which always 403s, so a working key was reported as rejected while chat succeeded. Both probe URLs now go through normalize_gemini_base_url(base, api_key), the same routing the chat client uses.
This commit is contained in:
@@ -210,6 +210,9 @@ def _apikey_request(key: str, base_env, default_url) -> tuple:
|
||||
# Google's Generative Language API rejects ``Authorization: Bearer <api-key>`` with 401
|
||||
# ACCESS_TOKEN_TYPE_UNSUPPORTED (reserved for OAuth 2 tokens); plain keys use ``x-goog-api-key``.
|
||||
if url and base_url_host_matches(url, "generativelanguage.googleapis.com"):
|
||||
from agent.gemini_native_adapter import normalize_gemini_base_url
|
||||
# A Vertex express key (AQ.) can only 403 on the Studio host; normalize routes it to aiplatform.
|
||||
url = normalize_gemini_base_url(url.rsplit("/models", 1)[0], key) + "/models"
|
||||
headers.pop("Authorization", None)
|
||||
headers["x-goog-api-key"] = key
|
||||
return base, url, headers
|
||||
|
||||
@@ -736,6 +736,10 @@ async def validate_provider_credential(body: EnvVarUpdate, request: Request):
|
||||
return {"ok": True, "reachable": False, "message": ""}
|
||||
|
||||
url, auth = probe
|
||||
if key == "GEMINI_API_KEY":
|
||||
from agent.gemini_native_adapter import normalize_gemini_base_url
|
||||
# A Vertex express key (AQ.) can only 403 on the Studio host; normalize routes it to aiplatform.
|
||||
url = normalize_gemini_base_url(url.rsplit("/models", 1)[0], value) + "/models"
|
||||
headers = {"Accept": "application/json"}
|
||||
params = {}
|
||||
if auth == "bearer":
|
||||
|
||||
60
tests/hermes_cli/test_gemini_express_key_probes.py
Normal file
60
tests/hermes_cli/test_gemini_express_key_probes.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""Key-validation surfaces route Vertex AI express keys (``AQ.``) to aiplatform, matching chat (#114335).
|
||||
|
||||
Sending an express key to generativelanguage.googleapis.com always 403s, so ``hermes doctor`` and the
|
||||
dashboard key test would report a working key as rejected while chat succeeded.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent.gemini_native_adapter import VERTEX_EXPRESS_BASE_URL
|
||||
|
||||
_STUDIO_MODELS = "https://generativelanguage.googleapis.com/v1beta/models"
|
||||
|
||||
|
||||
def test_doctor_gemini_probe_routes_express_key_to_aiplatform():
|
||||
from hermes_cli.doctor_connectivity import _apikey_request
|
||||
|
||||
_, url, headers = _apikey_request("AQ.express-key", None, _STUDIO_MODELS)
|
||||
assert url == VERTEX_EXPRESS_BASE_URL + "/models"
|
||||
assert headers["x-goog-api-key"] == "AQ.express-key" and "Authorization" not in headers
|
||||
|
||||
# AI Studio keys keep hitting the Studio host.
|
||||
assert _apikey_request("AIza-studio-key", None, _STUDIO_MODELS)[1] == _STUDIO_MODELS
|
||||
|
||||
|
||||
def test_dashboard_gemini_key_probe_routes_express_key_to_aiplatform(monkeypatch):
|
||||
import hermes_cli.web_routers.config_env as mod
|
||||
from hermes_cli.web_models import EnvVarUpdate
|
||||
|
||||
seen = {}
|
||||
|
||||
class _Resp:
|
||||
status_code = 200
|
||||
is_success = True
|
||||
|
||||
class _Client:
|
||||
def __init__(self, *a, **k):
|
||||
pass
|
||||
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *a):
|
||||
return False
|
||||
|
||||
async def get(self, url, **k):
|
||||
seen["url"] = url
|
||||
return _Resp()
|
||||
|
||||
import httpx
|
||||
|
||||
monkeypatch.setattr(httpx, "AsyncClient", _Client)
|
||||
monkeypatch.setattr(mod, "_require_token", lambda request: None)
|
||||
|
||||
body = EnvVarUpdate(key="GEMINI_API_KEY", value="AQ.express-key")
|
||||
out = asyncio.run(mod.validate_provider_credential(body, request=None)) # type: ignore[arg-type]
|
||||
|
||||
assert out["ok"] is True
|
||||
assert seen["url"] == VERTEX_EXPRESS_BASE_URL + "/models"
|
||||
Reference in New Issue
Block a user