mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-03 02:11:48 +00:00
Merge: auto-save custom endpoints + removal option
This commit is contained in:
commit
ff3f3169b2
1 changed files with 114 additions and 1 deletions
|
|
@ -787,6 +787,10 @@ def cmd_model(args):
|
||||||
# Always add the manual custom endpoint option last
|
# Always add the manual custom endpoint option last
|
||||||
providers.append(("custom", "Custom endpoint (enter URL manually)"))
|
providers.append(("custom", "Custom endpoint (enter URL manually)"))
|
||||||
|
|
||||||
|
# Add removal option if there are saved custom providers
|
||||||
|
if _custom_provider_map:
|
||||||
|
providers.append(("remove-custom", "Remove a saved custom provider"))
|
||||||
|
|
||||||
# Reorder so the active provider is at the top
|
# Reorder so the active provider is at the top
|
||||||
known_keys = {k for k, _ in providers}
|
known_keys = {k for k, _ in providers}
|
||||||
active_key = active if active in known_keys else "custom"
|
active_key = active if active in known_keys else "custom"
|
||||||
|
|
@ -816,6 +820,8 @@ def cmd_model(args):
|
||||||
_model_flow_custom(config)
|
_model_flow_custom(config)
|
||||||
elif selected_provider.startswith("custom:") and selected_provider in _custom_provider_map:
|
elif selected_provider.startswith("custom:") and selected_provider in _custom_provider_map:
|
||||||
_model_flow_named_custom(config, _custom_provider_map[selected_provider])
|
_model_flow_named_custom(config, _custom_provider_map[selected_provider])
|
||||||
|
elif selected_provider == "remove-custom":
|
||||||
|
_remove_custom_provider(config)
|
||||||
elif selected_provider in ("zai", "kimi-coding", "minimax", "minimax-cn"):
|
elif selected_provider in ("zai", "kimi-coding", "minimax", "minimax-cn"):
|
||||||
_model_flow_api_key_provider(config, selected_provider, current_model)
|
_model_flow_api_key_provider(config, selected_provider, current_model)
|
||||||
|
|
||||||
|
|
@ -1031,7 +1037,11 @@ def _model_flow_openai_codex(config, current_model=""):
|
||||||
|
|
||||||
|
|
||||||
def _model_flow_custom(config):
|
def _model_flow_custom(config):
|
||||||
"""Custom endpoint: collect URL, API key, and model name."""
|
"""Custom endpoint: collect URL, API key, and model name.
|
||||||
|
|
||||||
|
Automatically saves the endpoint to ``custom_providers`` in config.yaml
|
||||||
|
so it appears in the provider menu on subsequent runs.
|
||||||
|
"""
|
||||||
from hermes_cli.auth import _save_model_choice, deactivate_provider
|
from hermes_cli.auth import _save_model_choice, deactivate_provider
|
||||||
from hermes_cli.config import get_env_value, save_env_value, load_config, save_config
|
from hermes_cli.config import get_env_value, save_env_value, load_config, save_config
|
||||||
|
|
||||||
|
|
@ -1063,6 +1073,8 @@ def _model_flow_custom(config):
|
||||||
print(f"Invalid URL: {effective_url} (must start with http:// or https://)")
|
print(f"Invalid URL: {effective_url} (must start with http:// or https://)")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
effective_key = api_key or current_key
|
||||||
|
|
||||||
if base_url:
|
if base_url:
|
||||||
save_env_value("OPENAI_BASE_URL", base_url)
|
save_env_value("OPENAI_BASE_URL", base_url)
|
||||||
if api_key:
|
if api_key:
|
||||||
|
|
@ -1086,6 +1098,107 @@ def _model_flow_custom(config):
|
||||||
deactivate_provider()
|
deactivate_provider()
|
||||||
print("Endpoint saved. Use `/model` in chat or `hermes model` to set a model.")
|
print("Endpoint saved. Use `/model` in chat or `hermes model` to set a model.")
|
||||||
|
|
||||||
|
# Auto-save to custom_providers so it appears in the menu next time
|
||||||
|
_save_custom_provider(effective_url, effective_key)
|
||||||
|
|
||||||
|
|
||||||
|
def _save_custom_provider(base_url, api_key=""):
|
||||||
|
"""Save a custom endpoint to custom_providers in config.yaml.
|
||||||
|
|
||||||
|
Deduplicates by base_url — if the URL already exists, silently skips.
|
||||||
|
Auto-generates a name from the URL hostname.
|
||||||
|
"""
|
||||||
|
from hermes_cli.config import load_config, save_config
|
||||||
|
|
||||||
|
cfg = load_config()
|
||||||
|
providers = cfg.get("custom_providers") or []
|
||||||
|
if not isinstance(providers, list):
|
||||||
|
providers = []
|
||||||
|
|
||||||
|
# Check if this URL is already saved
|
||||||
|
for entry in providers:
|
||||||
|
if isinstance(entry, dict) and entry.get("base_url", "").rstrip("/") == base_url.rstrip("/"):
|
||||||
|
return # already saved
|
||||||
|
|
||||||
|
# Auto-generate a name from the URL
|
||||||
|
import re
|
||||||
|
clean = base_url.replace("https://", "").replace("http://", "").rstrip("/")
|
||||||
|
# Remove /v1 suffix for cleaner names
|
||||||
|
clean = re.sub(r"/v1/?$", "", clean)
|
||||||
|
# Use hostname:port as the name
|
||||||
|
name = clean.split("/")[0]
|
||||||
|
# Capitalize for readability
|
||||||
|
if "localhost" in name or "127.0.0.1" in name:
|
||||||
|
name = f"Local ({name})"
|
||||||
|
elif "runpod" in name.lower():
|
||||||
|
name = f"RunPod ({name})"
|
||||||
|
else:
|
||||||
|
name = name.capitalize()
|
||||||
|
|
||||||
|
entry = {"name": name, "base_url": base_url}
|
||||||
|
if api_key:
|
||||||
|
entry["api_key"] = api_key
|
||||||
|
|
||||||
|
providers.append(entry)
|
||||||
|
cfg["custom_providers"] = providers
|
||||||
|
save_config(cfg)
|
||||||
|
print(f" 💾 Saved to custom providers as \"{name}\" (edit in config.yaml)")
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_custom_provider(config):
|
||||||
|
"""Let the user remove a saved custom provider from config.yaml."""
|
||||||
|
from hermes_cli.config import load_config, save_config
|
||||||
|
|
||||||
|
cfg = load_config()
|
||||||
|
providers = cfg.get("custom_providers") or []
|
||||||
|
if not isinstance(providers, list) or not providers:
|
||||||
|
print("No custom providers configured.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("Remove a custom provider:\n")
|
||||||
|
|
||||||
|
choices = []
|
||||||
|
for entry in providers:
|
||||||
|
if isinstance(entry, dict):
|
||||||
|
name = entry.get("name", "unnamed")
|
||||||
|
url = entry.get("base_url", "")
|
||||||
|
short_url = url.replace("https://", "").replace("http://", "").rstrip("/")
|
||||||
|
choices.append(f"{name} ({short_url})")
|
||||||
|
else:
|
||||||
|
choices.append(str(entry))
|
||||||
|
choices.append("Cancel")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from simple_term_menu import TerminalMenu
|
||||||
|
menu = TerminalMenu(
|
||||||
|
[f" {c}" for c in choices], cursor_index=0,
|
||||||
|
menu_cursor="-> ", menu_cursor_style=("fg_red", "bold"),
|
||||||
|
menu_highlight_style=("fg_red",),
|
||||||
|
cycle_cursor=True, clear_screen=False,
|
||||||
|
title="Select provider to remove:",
|
||||||
|
)
|
||||||
|
idx = menu.show()
|
||||||
|
print()
|
||||||
|
except (ImportError, NotImplementedError):
|
||||||
|
for i, c in enumerate(choices, 1):
|
||||||
|
print(f" {i}. {c}")
|
||||||
|
print()
|
||||||
|
try:
|
||||||
|
val = input(f"Choice [1-{len(choices)}]: ").strip()
|
||||||
|
idx = int(val) - 1 if val else None
|
||||||
|
except (ValueError, KeyboardInterrupt, EOFError):
|
||||||
|
idx = None
|
||||||
|
|
||||||
|
if idx is None or idx >= len(providers):
|
||||||
|
print("No change.")
|
||||||
|
return
|
||||||
|
|
||||||
|
removed = providers.pop(idx)
|
||||||
|
cfg["custom_providers"] = providers
|
||||||
|
save_config(cfg)
|
||||||
|
removed_name = removed.get("name", "unnamed") if isinstance(removed, dict) else str(removed)
|
||||||
|
print(f"✅ Removed \"{removed_name}\" from custom providers.")
|
||||||
|
|
||||||
|
|
||||||
def _model_flow_named_custom(config, provider_info):
|
def _model_flow_named_custom(config, provider_info):
|
||||||
"""Handle a named custom provider from config.yaml custom_providers list."""
|
"""Handle a named custom provider from config.yaml custom_providers list."""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue