fix(gemini): restrict TTS client context to official host

This commit is contained in:
Vishal Dharmadhikari 2026-07-10 16:05:35 -07:00 committed by Teknium
parent b8eb89f5c9
commit 226e8de827
2 changed files with 31 additions and 15 deletions

View file

@ -117,6 +117,7 @@ class TestGenerateGeminiTts:
def test_x_goog_api_client_header_is_set(self, tmp_path, monkeypatch, mock_gemini_response):
"""Gemini TTS requests should include Hermes client context."""
from hermes_cli import __version__
from tools.tts_tool import _generate_gemini_tts
monkeypatch.setenv("GEMINI_API_KEY", "test-key")
@ -125,8 +126,7 @@ class TestGenerateGeminiTts:
_generate_gemini_tts("Hi", str(tmp_path / "test.wav"), {})
headers = mock_post.call_args[1]["headers"]
assert "X-Goog-Api-Client" in headers
assert headers["X-Goog-Api-Client"].startswith("hermes-agent/")
assert headers["X-Goog-Api-Client"] == f"hermes-agent/{__version__}"
def test_default_voice_and_model(self, tmp_path, monkeypatch, mock_gemini_response):
from tools.tts_tool import (
@ -268,6 +268,22 @@ class TestGenerateGeminiTts:
_generate_gemini_tts("Hi", str(tmp_path / "test.wav"), {})
assert mock_post.call_args[0][0].startswith("https://custom-gemini.example.com/v1beta/")
assert "X-Goog-Api-Client" not in mock_post.call_args[1]["headers"]
def test_lookalike_base_url_omits_client_context(
self, tmp_path, monkeypatch, mock_gemini_response
):
from tools.tts_tool import _generate_gemini_tts
lookalike = "https://generativelanguage.googleapis.com.evil.example/v1beta"
monkeypatch.setenv("GEMINI_API_KEY", "test-key")
monkeypatch.setenv("GEMINI_BASE_URL", lookalike)
with patch("requests.post", return_value=mock_gemini_response) as mock_post:
_generate_gemini_tts("Hi", str(tmp_path / "test.wav"), {})
assert mock_post.call_args[0][0].startswith(f"{lookalike}/")
assert "X-Goog-Api-Client" not in mock_post.call_args[1]["headers"]
def test_persona_prompt_file_appends_labeled_transcript(
self, tmp_path, monkeypatch, mock_gemini_response

View file

@ -50,7 +50,7 @@ import threading
import uuid
from pathlib import Path
from typing import Callable, Dict, Any, Optional
from urllib.parse import urljoin
from urllib.parse import urljoin, urlparse
from hermes_cli._subprocess_compat import windows_hide_flags
from hermes_constants import display_hermes_home
@ -1850,24 +1850,24 @@ def _generate_gemini_tts(text: str, output_path: str, tts_config: Dict[str, Any]
},
}
try:
import hermes_cli as _hermes_cli
headers = {"Content-Type": "application/json"}
if urlparse(base_url).hostname == "generativelanguage.googleapis.com":
try:
import hermes_cli as _hermes_cli
_hermes_version = str(_hermes_cli.__version__)
except Exception:
_hermes_version = "0.0.0"
_hermes_version = str(_hermes_cli.__version__)
except Exception:
_hermes_version = "0.0.0"
# Include Hermes client context following Gemini's partner
# integration guidance:
# https://ai.google.dev/gemini-api/docs/partner-integration
headers["X-Goog-Api-Client"] = f"hermes-agent/{_hermes_version}"
endpoint = f"{base_url}/models/{model}:generateContent"
response = requests.post(
endpoint,
params={"key": api_key},
headers={
"Content-Type": "application/json",
# Include Hermes client context following Gemini's partner
# integration guidance:
# https://ai.google.dev/gemini-api/docs/partner-integration
"X-Goog-Api-Client": f"hermes-agent/{_hermes_version}",
},
headers=headers,
json=payload,
timeout=60,
)