mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 06:51:16 +08:00
fix: use hermes-agent.nousresearch.com as OpenRouter HTTP-Referer
* fix: stop rejecting unlisted models + auto-detect from /models endpoint validate_requested_model() now accepts models not in the provider's API listing with a warning instead of blocking. Removes hardcoded catalog fallback for validation — if API is unreachable, accepts with a warning. Model selection flows (setup + /model command) now probe the provider's /models endpoint to get the real available models. Falls back to hardcoded defaults with a clear warning when auto-detection fails: 'Could not auto-detect models — use Custom model if yours isn't listed.' Z.AI setup no longer excludes GLM-5 on coding plans. * fix: use hermes-agent.nousresearch.com as HTTP-Referer for OpenRouter OpenRouter scrapes the favicon/logo from the HTTP-Referer URL for app rankings. We were sending the GitHub repo URL, which gives us a generic GitHub logo. Changed to the proper website URL so our actual branding shows up in rankings. Changed in run_agent.py (main agent client) and auxiliary_client.py (vision/summarization clients).
This commit is contained in:
@@ -55,7 +55,7 @@ _API_KEY_PROVIDER_AUX_MODELS: Dict[str, str] = {
|
|||||||
|
|
||||||
# OpenRouter app attribution headers
|
# OpenRouter app attribution headers
|
||||||
_OR_HEADERS = {
|
_OR_HEADERS = {
|
||||||
"HTTP-Referer": "https://github.com/NousResearch/hermes-agent",
|
"HTTP-Referer": "https://hermes-agent.nousresearch.com",
|
||||||
"X-OpenRouter-Title": "Hermes Agent",
|
"X-OpenRouter-Title": "Hermes Agent",
|
||||||
"X-OpenRouter-Categories": "productivity,cli-agent",
|
"X-OpenRouter-Categories": "productivity,cli-agent",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1523,8 +1523,21 @@ def _model_flow_api_key_provider(config, provider_id, current_model=""):
|
|||||||
save_env_value(base_url_env, override)
|
save_env_value(base_url_env, override)
|
||||||
effective_base = override
|
effective_base = override
|
||||||
|
|
||||||
# Model selection
|
# Model selection — try live /models endpoint first, fall back to defaults
|
||||||
model_list = _PROVIDER_MODELS.get(provider_id, [])
|
from hermes_cli.models import fetch_api_models
|
||||||
|
api_key_for_probe = existing_key or (get_env_value(key_env) if key_env else "")
|
||||||
|
live_models = fetch_api_models(api_key_for_probe, effective_base)
|
||||||
|
|
||||||
|
if live_models:
|
||||||
|
model_list = live_models
|
||||||
|
print(f" Found {len(model_list)} model(s) from {pconfig.name} API")
|
||||||
|
else:
|
||||||
|
model_list = _PROVIDER_MODELS.get(provider_id, [])
|
||||||
|
if model_list:
|
||||||
|
print(f" ⚠ Could not auto-detect models from API — showing defaults.")
|
||||||
|
print(f" Use \"Enter custom model name\" if you don't see your model.")
|
||||||
|
# else: no defaults either, will fall through to raw input
|
||||||
|
|
||||||
if model_list:
|
if model_list:
|
||||||
selected = _prompt_model_selection(model_list, current_model=current_model)
|
selected = _prompt_model_selection(model_list, current_model=current_model)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -52,6 +52,68 @@ def _set_default_model(config: Dict[str, Any], model_name: str) -> None:
|
|||||||
config["model"] = model_cfg
|
config["model"] = model_cfg
|
||||||
|
|
||||||
|
|
||||||
|
# Default model lists per provider — used as fallback when the live
|
||||||
|
# /models endpoint can't be reached.
|
||||||
|
_DEFAULT_PROVIDER_MODELS = {
|
||||||
|
"zai": ["glm-5", "glm-4.7", "glm-4.5", "glm-4.5-flash"],
|
||||||
|
"kimi-coding": ["kimi-k2.5", "kimi-k2-thinking", "kimi-k2-turbo-preview"],
|
||||||
|
"minimax": ["MiniMax-M2.5", "MiniMax-M2.5-highspeed", "MiniMax-M2.1"],
|
||||||
|
"minimax-cn": ["MiniMax-M2.5", "MiniMax-M2.5-highspeed", "MiniMax-M2.1"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _setup_provider_model_selection(config, provider_id, current_model, prompt_choice, prompt_fn):
|
||||||
|
"""Model selection for API-key providers with live /models detection.
|
||||||
|
|
||||||
|
Tries the provider's /models endpoint first. Falls back to a
|
||||||
|
hardcoded default list with a warning if the endpoint is unreachable.
|
||||||
|
Always offers a 'Custom model' escape hatch.
|
||||||
|
"""
|
||||||
|
from hermes_cli.auth import PROVIDER_REGISTRY
|
||||||
|
from hermes_cli.config import get_env_value
|
||||||
|
from hermes_cli.models import fetch_api_models
|
||||||
|
|
||||||
|
pconfig = PROVIDER_REGISTRY[provider_id]
|
||||||
|
|
||||||
|
# Resolve API key and base URL for the probe
|
||||||
|
api_key = ""
|
||||||
|
for ev in pconfig.api_key_env_vars:
|
||||||
|
api_key = get_env_value(ev) or os.getenv(ev, "")
|
||||||
|
if api_key:
|
||||||
|
break
|
||||||
|
base_url_env = pconfig.base_url_env_var or ""
|
||||||
|
base_url = (get_env_value(base_url_env) if base_url_env else "") or pconfig.inference_base_url
|
||||||
|
|
||||||
|
# Try live /models endpoint
|
||||||
|
live_models = fetch_api_models(api_key, base_url)
|
||||||
|
|
||||||
|
if live_models:
|
||||||
|
provider_models = live_models
|
||||||
|
print_info(f"Found {len(live_models)} model(s) from {pconfig.name} API")
|
||||||
|
else:
|
||||||
|
provider_models = _DEFAULT_PROVIDER_MODELS.get(provider_id, [])
|
||||||
|
if provider_models:
|
||||||
|
print_warning(
|
||||||
|
f"Could not auto-detect models from {pconfig.name} API — showing defaults.\n"
|
||||||
|
f" Use \"Custom model\" if the model you expect isn't listed."
|
||||||
|
)
|
||||||
|
|
||||||
|
model_choices = list(provider_models)
|
||||||
|
model_choices.append("Custom model")
|
||||||
|
model_choices.append(f"Keep current ({current_model})")
|
||||||
|
|
||||||
|
keep_idx = len(model_choices) - 1
|
||||||
|
model_idx = prompt_choice("Select default model:", model_choices, keep_idx)
|
||||||
|
|
||||||
|
if model_idx < len(provider_models):
|
||||||
|
_set_default_model(config, provider_models[model_idx])
|
||||||
|
elif model_idx == len(provider_models):
|
||||||
|
custom = prompt_fn("Enter model name")
|
||||||
|
if custom:
|
||||||
|
_set_default_model(config, custom)
|
||||||
|
# else: keep current
|
||||||
|
|
||||||
|
|
||||||
def _sync_model_from_disk(config: Dict[str, Any]) -> None:
|
def _sync_model_from_disk(config: Dict[str, Any]) -> None:
|
||||||
disk_model = load_config().get("model")
|
disk_model = load_config().get("model")
|
||||||
if isinstance(disk_model, dict):
|
if isinstance(disk_model, dict):
|
||||||
@@ -1107,58 +1169,11 @@ def setup_model_provider(config: dict):
|
|||||||
_set_default_model(config, custom)
|
_set_default_model(config, custom)
|
||||||
_update_config_for_provider("openai-codex", DEFAULT_CODEX_BASE_URL)
|
_update_config_for_provider("openai-codex", DEFAULT_CODEX_BASE_URL)
|
||||||
_set_model_provider(config, "openai-codex", DEFAULT_CODEX_BASE_URL)
|
_set_model_provider(config, "openai-codex", DEFAULT_CODEX_BASE_URL)
|
||||||
elif selected_provider == "zai":
|
elif selected_provider in ("zai", "kimi-coding", "minimax", "minimax-cn"):
|
||||||
# Always offer all models — Pro/Max plans support GLM-5 even
|
_setup_provider_model_selection(
|
||||||
# on coding endpoints. If the user's plan doesn't support a
|
config, selected_provider, current_model,
|
||||||
# model, the API will return an error at runtime (not our job
|
prompt_choice, prompt,
|
||||||
# to gatekeep).
|
)
|
||||||
zai_models = ["glm-5", "glm-4.7", "glm-4.5", "glm-4.5-flash"]
|
|
||||||
model_choices = list(zai_models)
|
|
||||||
model_choices.append("Custom model")
|
|
||||||
model_choices.append(f"Keep current ({current_model})")
|
|
||||||
|
|
||||||
keep_idx = len(model_choices) - 1
|
|
||||||
model_idx = prompt_choice("Select default model:", model_choices, keep_idx)
|
|
||||||
|
|
||||||
if model_idx < len(zai_models):
|
|
||||||
_set_default_model(config, zai_models[model_idx])
|
|
||||||
elif model_idx == len(zai_models):
|
|
||||||
custom = prompt("Enter model name")
|
|
||||||
if custom:
|
|
||||||
_set_default_model(config, custom)
|
|
||||||
# else: keep current
|
|
||||||
elif selected_provider == "kimi-coding":
|
|
||||||
kimi_models = ["kimi-k2.5", "kimi-k2-thinking", "kimi-k2-turbo-preview"]
|
|
||||||
model_choices = list(kimi_models)
|
|
||||||
model_choices.append("Custom model")
|
|
||||||
model_choices.append(f"Keep current ({current_model})")
|
|
||||||
|
|
||||||
keep_idx = len(model_choices) - 1
|
|
||||||
model_idx = prompt_choice("Select default model:", model_choices, keep_idx)
|
|
||||||
|
|
||||||
if model_idx < len(kimi_models):
|
|
||||||
_set_default_model(config, kimi_models[model_idx])
|
|
||||||
elif model_idx == len(kimi_models):
|
|
||||||
custom = prompt("Enter model name")
|
|
||||||
if custom:
|
|
||||||
_set_default_model(config, custom)
|
|
||||||
# else: keep current
|
|
||||||
elif selected_provider in ("minimax", "minimax-cn"):
|
|
||||||
minimax_models = ["MiniMax-M2.5", "MiniMax-M2.5-highspeed", "MiniMax-M2.1"]
|
|
||||||
model_choices = list(minimax_models)
|
|
||||||
model_choices.append("Custom model")
|
|
||||||
model_choices.append(f"Keep current ({current_model})")
|
|
||||||
|
|
||||||
keep_idx = len(model_choices) - 1
|
|
||||||
model_idx = prompt_choice("Select default model:", model_choices, keep_idx)
|
|
||||||
|
|
||||||
if model_idx < len(minimax_models):
|
|
||||||
_set_default_model(config, minimax_models[model_idx])
|
|
||||||
elif model_idx == len(minimax_models):
|
|
||||||
custom = prompt("Enter model name")
|
|
||||||
if custom:
|
|
||||||
_set_default_model(config, custom)
|
|
||||||
# else: keep current
|
|
||||||
else:
|
else:
|
||||||
# Static list for OpenRouter / fallback (from canonical list)
|
# Static list for OpenRouter / fallback (from canonical list)
|
||||||
from hermes_cli.models import model_ids, menu_labels
|
from hermes_cli.models import model_ids, menu_labels
|
||||||
|
|||||||
@@ -432,7 +432,7 @@ class AIAgent:
|
|||||||
effective_base = base_url
|
effective_base = base_url
|
||||||
if "openrouter" in effective_base.lower():
|
if "openrouter" in effective_base.lower():
|
||||||
client_kwargs["default_headers"] = {
|
client_kwargs["default_headers"] = {
|
||||||
"HTTP-Referer": "https://github.com/NousResearch/hermes-agent",
|
"HTTP-Referer": "https://hermes-agent.nousresearch.com",
|
||||||
"X-OpenRouter-Title": "Hermes Agent",
|
"X-OpenRouter-Title": "Hermes Agent",
|
||||||
"X-OpenRouter-Categories": "productivity,cli-agent",
|
"X-OpenRouter-Categories": "productivity,cli-agent",
|
||||||
}
|
}
|
||||||
@@ -459,7 +459,7 @@ class AIAgent:
|
|||||||
"api_key": os.getenv("OPENROUTER_API_KEY", ""),
|
"api_key": os.getenv("OPENROUTER_API_KEY", ""),
|
||||||
"base_url": OPENROUTER_BASE_URL,
|
"base_url": OPENROUTER_BASE_URL,
|
||||||
"default_headers": {
|
"default_headers": {
|
||||||
"HTTP-Referer": "https://github.com/NousResearch/hermes-agent",
|
"HTTP-Referer": "https://hermes-agent.nousresearch.com",
|
||||||
"X-OpenRouter-Title": "Hermes Agent",
|
"X-OpenRouter-Title": "Hermes Agent",
|
||||||
"X-OpenRouter-Categories": "productivity,cli-agent",
|
"X-OpenRouter-Categories": "productivity,cli-agent",
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user