Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions hermes_cli/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,22 @@ class ProviderConfig:
api_key_env_vars=("MINIMAX_CN_API_KEY",),
base_url_env_var="MINIMAX_CN_BASE_URL",
),
"gemini": ProviderConfig(
id="gemini",
name="Google Gemini",
auth_type="api_key",
inference_base_url="https://generativelanguage.googleapis.com/v1beta/openai",
api_key_env_vars=("GEMINI_API_KEY", "GOOGLE_API_KEY"),
base_url_env_var="GEMINI_BASE_URL",
),
"huggingface": ProviderConfig(
id="huggingface",
name="Hugging Face Inference API",
auth_type="api_key",
inference_base_url="https://api-inference.huggingface.co/v1",
api_key_env_vars=("HUGGINGFACE_API_KEY", "HF_TOKEN"),
base_url_env_var="HUGGINGFACE_BASE_URL",
),
}


Expand Down
66 changes: 65 additions & 1 deletion hermes_cli/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,8 @@ def setup_model_provider(config: dict):
"Kimi / Moonshot (Kimi coding models)",
"MiniMax (global endpoint)",
"MiniMax China (mainland China endpoint)",
"Google Gemini (free tier available)",
"Hugging Face Inference API (free tier available)",
]
if keep_label:
provider_choices.append(keep_label)
Expand Down Expand Up @@ -1041,7 +1043,67 @@ def setup_model_provider(config: dict):
_update_config_for_provider("minimax-cn", pconfig.inference_base_url)
_set_model_provider(config, "minimax-cn", pconfig.inference_base_url)

# else: provider_idx == 9 (Keep current) — only shown when a provider already exists
elif provider_idx == 9: # Google Gemini
selected_provider = "gemini"
print()
print_header("Google Gemini API Key")
pconfig = PROVIDER_REGISTRY["gemini"]
print_info(f"Provider: {pconfig.name}")
print_info("Get your free API key at: https://aistudio.google.com/apikey")
print_info("Free tier available — no credit card required.")
print()
existing_key = get_env_value("GEMINI_API_KEY") or get_env_value("GOOGLE_API_KEY")
if existing_key:
print_info(f"Current: {existing_key[:8]}... (configured)")
if prompt_yes_no("Update API key?", False):
new_key = prompt(" Gemini API key", password=True)
if new_key:
save_env_value("GEMINI_API_KEY", new_key)
print_success("Gemini API key updated")
else:
api_key = prompt(" Gemini API key", password=True)
if api_key:
save_env_value("GEMINI_API_KEY", api_key)
print_success("Gemini API key saved")
else:
print_warning("Skipped - agent won't work without an API key")
if existing_custom:
save_env_value("OPENAI_BASE_URL", "")
save_env_value("OPENAI_API_KEY", "")
_update_config_for_provider("gemini", pconfig.inference_base_url)
_set_model_provider(config, "gemini", pconfig.inference_base_url)

elif provider_idx == 10: # Hugging Face
selected_provider = "huggingface"
print()
print_header("Hugging Face Inference API Key")
pconfig = PROVIDER_REGISTRY["huggingface"]
print_info(f"Provider: {pconfig.name}")
print_info("Get your API token at: https://huggingface.co/settings/tokens")
print_info("Free tier available for many models.")
print()
existing_key = get_env_value("HUGGINGFACE_API_KEY") or get_env_value("HF_TOKEN")
if existing_key:
print_info(f"Current: {existing_key[:8]}... (configured)")
if prompt_yes_no("Update API key?", False):
new_key = prompt(" Hugging Face API token", password=True)
if new_key:
save_env_value("HUGGINGFACE_API_KEY", new_key)
print_success("Hugging Face API token updated")
else:
api_key = prompt(" Hugging Face API token", password=True)
if api_key:
save_env_value("HUGGINGFACE_API_KEY", api_key)
print_success("Hugging Face API token saved")
else:
print_warning("Skipped - agent won't work without an API token")
if existing_custom:
save_env_value("OPENAI_BASE_URL", "")
save_env_value("OPENAI_API_KEY", "")
_update_config_for_provider("huggingface", pconfig.inference_base_url)
_set_model_provider(config, "huggingface", pconfig.inference_base_url)

# else: provider_idx == 11 (Keep current) — only shown when a provider already exists

# ── OpenRouter API Key for tools (if not already set) ──
# Tools (vision, web, MoA) use OpenRouter independently of the main provider.
Expand All @@ -1055,6 +1117,8 @@ def setup_model_provider(config: dict):
"kimi-coding",
"minimax",
"minimax-cn",
"gemini",
"huggingface",
) and not get_env_value("OPENROUTER_API_KEY"):
print()
print_header("OpenRouter API Key (for tools)")
Expand Down
41 changes: 41 additions & 0 deletions tests/hermes_cli/test_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,44 @@ def test_custom_setup_clears_active_oauth_provider(tmp_path, monkeypatch):
assert reloaded["model"]["provider"] == "custom"
assert reloaded["model"]["base_url"] == "https://custom.example/v1"
assert reloaded["model"]["default"] == "custom/model"


class TestGeminiHuggingFaceProviders:
"""Tests for Gemini and HuggingFace provider registry entries."""

def test_gemini_in_provider_registry(self):
from hermes_cli.auth import PROVIDER_REGISTRY
assert "gemini" in PROVIDER_REGISTRY
p = PROVIDER_REGISTRY["gemini"]
assert p.auth_type == "api_key"
assert "googleapis.com" in p.inference_base_url
assert "GEMINI_API_KEY" in p.api_key_env_vars

def test_huggingface_in_provider_registry(self):
from hermes_cli.auth import PROVIDER_REGISTRY
assert "huggingface" in PROVIDER_REGISTRY
p = PROVIDER_REGISTRY["huggingface"]
assert p.auth_type == "api_key"
assert "huggingface.co" in p.inference_base_url
assert "HUGGINGFACE_API_KEY" in p.api_key_env_vars
assert "HF_TOKEN" in p.api_key_env_vars

def test_gemini_in_provider_choices(self):
"""Gemini appears in the setup wizard provider list."""
import hermes_cli.setup as setup_mod
import inspect
src = inspect.getsource(setup_mod.setup_model_provider)
assert "Google Gemini" in src

def test_huggingface_in_provider_choices(self):
"""HuggingFace appears in the setup wizard provider list."""
import hermes_cli.setup as setup_mod
import inspect
src = inspect.getsource(setup_mod.setup_model_provider)
assert "Hugging Face" in src

def test_gemini_google_api_key_fallback(self):
"""GOOGLE_API_KEY is accepted as fallback for Gemini."""
from hermes_cli.auth import PROVIDER_REGISTRY
p = PROVIDER_REGISTRY["gemini"]
assert "GOOGLE_API_KEY" in p.api_key_env_vars
Loading