mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-21 10:22:18 +00:00
fix(model): clear stale endpoint credentials across switches
This commit is contained in:
parent
95a3affc2e
commit
c253b07380
9 changed files with 187 additions and 17 deletions
|
|
@ -6386,16 +6386,12 @@ def _update_config_for_provider(
|
|||
# Clear stale base_url to prevent contamination when switching providers
|
||||
model_cfg.pop("base_url", None)
|
||||
|
||||
# Clear stale api_key/api_mode left over from a previous custom provider.
|
||||
# When the user switches from e.g. a MiniMax custom endpoint
|
||||
# (api_mode=anthropic_messages, api_key=mxp-...) to a built-in provider
|
||||
# (e.g. OpenRouter), the stale api_key/api_mode would override the new
|
||||
# provider's credentials and transport choice. Built-in providers that
|
||||
# need a specific api_mode (copilot, xai) set it at request-resolution
|
||||
# time via `_copilot_runtime_api_mode` / `_detect_api_mode_for_url`, so
|
||||
# removing the persisted value here is safe.
|
||||
model_cfg.pop("api_key", None)
|
||||
model_cfg.pop("api_mode", None)
|
||||
# Clear stale endpoint credentials left over from a previous custom provider.
|
||||
# Built-in providers resolve credentials from env/auth state, not inline
|
||||
# model.api_key.
|
||||
from hermes_cli.config import clear_model_endpoint_credentials
|
||||
|
||||
clear_model_endpoint_credentials(model_cfg)
|
||||
|
||||
# When switching to a non-OpenRouter provider, ensure model.default is
|
||||
# valid for the new provider. An OpenRouter-formatted name like
|
||||
|
|
|
|||
|
|
@ -3915,6 +3915,30 @@ def _set_nested(config, dotted_key: str, value):
|
|||
current[last] = value
|
||||
|
||||
|
||||
def clear_model_endpoint_credentials(
|
||||
model_cfg: Dict[str, Any],
|
||||
*,
|
||||
clear_api_key: bool = True,
|
||||
clear_api_mode: bool = True,
|
||||
) -> Dict[str, Any]:
|
||||
"""Remove stale inline endpoint credentials from a model config.
|
||||
|
||||
``model.api_key`` is valid only for explicit custom endpoint assignments.
|
||||
Built-in providers resolve credentials from env vars, auth.json, or the
|
||||
credential pool. When switching away from a custom endpoint, leaving these
|
||||
fields behind keeps secrets in config.yaml and can contaminate later custom
|
||||
resolution paths.
|
||||
"""
|
||||
if not isinstance(model_cfg, dict):
|
||||
return model_cfg
|
||||
if clear_api_key:
|
||||
model_cfg.pop("api_key", None)
|
||||
model_cfg.pop("api", None)
|
||||
if clear_api_mode:
|
||||
model_cfg.pop("api_mode", None)
|
||||
return model_cfg
|
||||
|
||||
|
||||
def get_missing_config_fields() -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Check which config fields are missing or outdated (recursive).
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ import argparse
|
|||
import os
|
||||
import subprocess
|
||||
|
||||
from hermes_cli.config import clear_model_endpoint_credentials
|
||||
|
||||
|
||||
def _prompt_auth_credentials_choice(title: str) -> str:
|
||||
"""Prompt for reuse / reauthenticate / cancel with the standard radio UI.
|
||||
|
|
@ -123,6 +125,7 @@ def _model_flow_openrouter(config, current_model=""):
|
|||
model["provider"] = "openrouter"
|
||||
model["base_url"] = OPENROUTER_BASE_URL
|
||||
model["api_mode"] = "chat_completions"
|
||||
clear_model_endpoint_credentials(model, clear_api_mode=False)
|
||||
save_config(cfg)
|
||||
deactivate_provider()
|
||||
print(f"Default model set to: {selected} (via OpenRouter)")
|
||||
|
|
@ -341,6 +344,7 @@ def _model_flow_nous(config, current_model="", args=None):
|
|||
model_cfg["base_url"] = inference_url.rstrip("/")
|
||||
else:
|
||||
model_cfg.pop("base_url", None)
|
||||
clear_model_endpoint_credentials(model_cfg)
|
||||
config["model"] = model_cfg
|
||||
# Clear any custom endpoint that might conflict
|
||||
if get_env_value("OPENAI_BASE_URL"):
|
||||
|
|
@ -1249,6 +1253,7 @@ def _model_flow_azure_foundry(config, current_model=""):
|
|||
model["api_mode"] = api_mode
|
||||
model["default"] = effective_model
|
||||
model["auth_mode"] = auth_mode_label
|
||||
clear_model_endpoint_credentials(model, clear_api_mode=False)
|
||||
if use_entra:
|
||||
# Persist only the non-default Entra scope so config.yaml stays tidy.
|
||||
# Azure identity selection stays in standard AZURE_* env vars.
|
||||
|
|
@ -1670,6 +1675,7 @@ def _model_flow_copilot(config, current_model=""):
|
|||
catalog=catalog,
|
||||
api_key=api_key,
|
||||
)
|
||||
clear_model_endpoint_credentials(model, clear_api_mode=False)
|
||||
if selected_effort is not None:
|
||||
_set_reasoning_effort(cfg, selected_effort)
|
||||
save_config(cfg)
|
||||
|
|
@ -1795,6 +1801,7 @@ def _model_flow_copilot_acp(config, current_model=""):
|
|||
model["provider"] = provider_id
|
||||
model["base_url"] = effective_base
|
||||
model["api_mode"] = "chat_completions"
|
||||
clear_model_endpoint_credentials(model, clear_api_mode=False)
|
||||
save_config(cfg)
|
||||
deactivate_provider()
|
||||
|
||||
|
|
@ -1884,6 +1891,7 @@ def _model_flow_kimi(config, current_model=""):
|
|||
model["provider"] = provider_id
|
||||
model["base_url"] = effective_base
|
||||
model.pop("api_mode", None) # let runtime auto-detect from URL
|
||||
clear_model_endpoint_credentials(model, clear_api_mode=False)
|
||||
save_config(cfg)
|
||||
deactivate_provider()
|
||||
|
||||
|
|
@ -1997,6 +2005,7 @@ def _model_flow_stepfun(config, current_model=""):
|
|||
model["provider"] = provider_id
|
||||
model["base_url"] = effective_base
|
||||
model.pop("api_mode", None)
|
||||
clear_model_endpoint_credentials(model, clear_api_mode=False)
|
||||
save_config(cfg)
|
||||
deactivate_provider()
|
||||
|
||||
|
|
@ -2080,6 +2089,7 @@ def _model_flow_bedrock_api_key(config, region, current_model=""):
|
|||
model["provider"] = "custom"
|
||||
model["base_url"] = mantle_base_url
|
||||
model.pop("api_mode", None) # chat_completions is the default
|
||||
clear_model_endpoint_credentials(model, clear_api_mode=False)
|
||||
|
||||
# Also save region in bedrock config for reference
|
||||
bedrock_cfg = cfg.get("bedrock", {})
|
||||
|
|
@ -2273,6 +2283,7 @@ def _model_flow_bedrock(config, current_model=""):
|
|||
model["provider"] = "bedrock"
|
||||
model["base_url"] = f"https://bedrock-runtime.{region}.amazonaws.com"
|
||||
model.pop("api_mode", None) # bedrock_converse is auto-detected
|
||||
clear_model_endpoint_credentials(model, clear_api_mode=False)
|
||||
|
||||
bedrock_cfg = cfg.get("bedrock", {})
|
||||
if not isinstance(bedrock_cfg, dict):
|
||||
|
|
@ -2566,6 +2577,7 @@ def _model_flow_api_key_provider(config, provider_id, current_model=""):
|
|||
cfg["model"] = model
|
||||
model["provider"] = provider_id
|
||||
model["base_url"] = effective_base
|
||||
clear_model_endpoint_credentials(model, clear_api_mode=False)
|
||||
if provider_id in {"opencode-zen", "opencode-go"}:
|
||||
model["api_mode"] = opencode_model_api_mode(provider_id, selected)
|
||||
else:
|
||||
|
|
@ -2720,6 +2732,7 @@ def _model_flow_anthropic(config, current_model=""):
|
|||
cfg["model"] = model
|
||||
model["provider"] = "anthropic"
|
||||
model.pop("base_url", None)
|
||||
clear_model_endpoint_credentials(model)
|
||||
save_config(cfg)
|
||||
deactivate_provider()
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ from hermes_cli.config import (
|
|||
cfg_get,
|
||||
DEFAULT_CONFIG,
|
||||
OPTIONAL_ENV_VARS,
|
||||
clear_model_endpoint_credentials,
|
||||
get_config_path,
|
||||
get_env_path,
|
||||
get_hermes_home,
|
||||
|
|
@ -901,8 +902,11 @@ def _apply_main_model_assignment(
|
|||
# same-provider re-pick so re-selecting a model doesn't wipe the key.
|
||||
if api_key.strip():
|
||||
model_cfg["api_key"] = api_key.strip()
|
||||
model_cfg.pop("api", None)
|
||||
elif model_cfg.get("api_key") and new_provider != prev_provider:
|
||||
model_cfg["api_key"] = ""
|
||||
clear_model_endpoint_credentials(model_cfg, clear_api_mode=False)
|
||||
if new_provider != prev_provider:
|
||||
clear_model_endpoint_credentials(model_cfg, clear_api_key=False)
|
||||
model_cfg.pop("context_length", None)
|
||||
return model_cfg
|
||||
|
||||
|
|
@ -3871,6 +3875,8 @@ def _apply_model_assignment_sync(
|
|||
slot_cfg = {}
|
||||
slot_cfg["provider"] = "auto"
|
||||
slot_cfg["model"] = ""
|
||||
slot_cfg.pop("base_url", None)
|
||||
clear_model_endpoint_credentials(slot_cfg)
|
||||
aux[slot] = slot_cfg
|
||||
cfg["auxiliary"] = aux
|
||||
save_config(cfg)
|
||||
|
|
@ -3886,8 +3892,13 @@ def _apply_model_assignment_sync(
|
|||
slot_cfg = aux.get(slot)
|
||||
if not isinstance(slot_cfg, dict):
|
||||
slot_cfg = {}
|
||||
prev_provider = str(slot_cfg.get("provider") or "").strip().lower()
|
||||
new_provider = provider.strip().lower()
|
||||
slot_cfg["provider"] = provider
|
||||
slot_cfg["model"] = model
|
||||
if new_provider != prev_provider and new_provider != "custom":
|
||||
slot_cfg.pop("base_url", None)
|
||||
clear_model_endpoint_credentials(slot_cfg)
|
||||
aux[slot] = slot_cfg
|
||||
|
||||
cfg["auxiliary"] = aux
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue