mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-14 09:11:54 +00:00
fix(tui): honor launch model overrides
This commit is contained in:
parent
ee0728c6c4
commit
e9c47c7042
4 changed files with 152 additions and 9 deletions
|
|
@ -1028,7 +1028,12 @@ def _make_tui_argv(tui_dir: Path, tui_dev: bool) -> tuple[list[str], Path]:
|
|||
return [node, str(root / "dist" / "entry.js")], root
|
||||
|
||||
|
||||
def _launch_tui(resume_session_id: Optional[str] = None, tui_dev: bool = False):
|
||||
def _launch_tui(
|
||||
resume_session_id: Optional[str] = None,
|
||||
tui_dev: bool = False,
|
||||
model: Optional[str] = None,
|
||||
provider: Optional[str] = None,
|
||||
):
|
||||
"""Replace current process with the TUI."""
|
||||
tui_dir = PROJECT_ROOT / "ui-tui"
|
||||
|
||||
|
|
@ -1038,6 +1043,12 @@ def _launch_tui(resume_session_id: Optional[str] = None, tui_dev: bool = False):
|
|||
)
|
||||
env.setdefault("HERMES_PYTHON", sys.executable)
|
||||
env.setdefault("HERMES_CWD", os.getcwd())
|
||||
if model:
|
||||
env["HERMES_MODEL"] = model
|
||||
env["HERMES_INFERENCE_MODEL"] = model
|
||||
if provider:
|
||||
env["HERMES_TUI_PROVIDER"] = provider
|
||||
env["HERMES_INFERENCE_PROVIDER"] = provider
|
||||
# Guarantee an 8GB V8 heap + exposed GC for the TUI. Default node cap is
|
||||
# ~1.5–4GB depending on version and can fatal-OOM on long sessions with
|
||||
# large transcripts / reasoning blobs. Token-level merge: respect any
|
||||
|
|
@ -1176,6 +1187,8 @@ def cmd_chat(args):
|
|||
_launch_tui(
|
||||
getattr(args, "resume", None),
|
||||
tui_dev=getattr(args, "tui_dev", False),
|
||||
model=getattr(args, "model", None),
|
||||
provider=getattr(args, "provider", None),
|
||||
)
|
||||
|
||||
# Import and run the CLI
|
||||
|
|
@ -6913,7 +6926,7 @@ For more help on a command:
|
|||
default=None,
|
||||
help=(
|
||||
"Model override for this invocation (e.g. anthropic/claude-sonnet-4.6). "
|
||||
"Applies to -z/--oneshot. Also settable via HERMES_INFERENCE_MODEL env var."
|
||||
"Applies to -z/--oneshot and --tui. Also settable via HERMES_INFERENCE_MODEL env var."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
|
|
@ -6921,7 +6934,7 @@ For more help on a command:
|
|||
default=None,
|
||||
help=(
|
||||
"Provider override for this invocation (e.g. openrouter, anthropic). "
|
||||
"Applies to -z/--oneshot. Also settable via HERMES_INFERENCE_PROVIDER env var."
|
||||
"Applies to -z/--oneshot and --tui. Also settable via HERMES_INFERENCE_PROVIDER env var."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from argparse import Namespace
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import types
|
||||
|
||||
|
|
@ -8,8 +9,11 @@ import pytest
|
|||
def _args(**overrides):
|
||||
base = {
|
||||
"continue_last": None,
|
||||
"model": None,
|
||||
"provider": None,
|
||||
"resume": None,
|
||||
"tui": True,
|
||||
"tui_dev": False,
|
||||
}
|
||||
base.update(overrides)
|
||||
return Namespace(**base)
|
||||
|
|
@ -31,7 +35,7 @@ def test_cmd_chat_tui_continue_uses_latest_tui_session(monkeypatch, main_mod):
|
|||
calls.append(source)
|
||||
return "20260408_235959_a1b2c3" if source == "tui" else None
|
||||
|
||||
def fake_launch(resume_session_id=None, tui_dev=False):
|
||||
def fake_launch(resume_session_id=None, tui_dev=False, model=None, provider=None):
|
||||
captured["resume"] = resume_session_id
|
||||
raise SystemExit(0)
|
||||
|
||||
|
|
@ -58,7 +62,7 @@ def test_cmd_chat_tui_continue_falls_back_to_latest_cli_session(monkeypatch, mai
|
|||
return "20260408_235959_d4e5f6"
|
||||
return None
|
||||
|
||||
def fake_launch(resume_session_id=None, tui_dev=False):
|
||||
def fake_launch(resume_session_id=None, tui_dev=False, model=None, provider=None):
|
||||
captured["resume"] = resume_session_id
|
||||
raise SystemExit(0)
|
||||
|
||||
|
|
@ -76,7 +80,7 @@ def test_cmd_chat_tui_continue_falls_back_to_latest_cli_session(monkeypatch, mai
|
|||
def test_cmd_chat_tui_resume_resolves_title_before_launch(monkeypatch, main_mod):
|
||||
captured = {}
|
||||
|
||||
def fake_launch(resume_session_id=None, tui_dev=False):
|
||||
def fake_launch(resume_session_id=None, tui_dev=False, model=None, provider=None):
|
||||
captured["resume"] = resume_session_id
|
||||
raise SystemExit(0)
|
||||
|
||||
|
|
@ -89,6 +93,60 @@ def test_cmd_chat_tui_resume_resolves_title_before_launch(monkeypatch, main_mod)
|
|||
assert captured["resume"] == "20260409_000000_aa11bb"
|
||||
|
||||
|
||||
def test_cmd_chat_tui_passes_model_and_provider(monkeypatch, main_mod):
|
||||
captured = {}
|
||||
|
||||
def fake_launch(resume_session_id=None, tui_dev=False, model=None, provider=None):
|
||||
captured.update(
|
||||
{
|
||||
"model": model,
|
||||
"provider": provider,
|
||||
"resume": resume_session_id,
|
||||
"tui_dev": tui_dev,
|
||||
}
|
||||
)
|
||||
raise SystemExit(0)
|
||||
|
||||
monkeypatch.setattr(main_mod, "_launch_tui", fake_launch)
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
main_mod.cmd_chat(
|
||||
_args(model="anthropic/claude-sonnet-4.6", provider="anthropic")
|
||||
)
|
||||
|
||||
assert captured == {
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"provider": "anthropic",
|
||||
"resume": None,
|
||||
"tui_dev": False,
|
||||
}
|
||||
|
||||
|
||||
def test_launch_tui_exports_model_and_provider(monkeypatch, main_mod):
|
||||
captured = {}
|
||||
|
||||
monkeypatch.setattr(
|
||||
main_mod,
|
||||
"_make_tui_argv",
|
||||
lambda tui_dir, tui_dev: (["node", "dist/entry.js"], Path(".")),
|
||||
)
|
||||
|
||||
def fake_call(argv, cwd=None, env=None):
|
||||
captured.update({"argv": argv, "cwd": cwd, "env": env})
|
||||
return 1
|
||||
|
||||
monkeypatch.setattr(main_mod.subprocess, "call", fake_call)
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
main_mod._launch_tui(model="nous/hermes-test", provider="nous")
|
||||
|
||||
env = captured["env"]
|
||||
assert env["HERMES_MODEL"] == "nous/hermes-test"
|
||||
assert env["HERMES_INFERENCE_MODEL"] == "nous/hermes-test"
|
||||
assert env["HERMES_TUI_PROVIDER"] == "nous"
|
||||
assert env["HERMES_INFERENCE_PROVIDER"] == "nous"
|
||||
|
||||
|
||||
def test_print_tui_exit_summary_includes_resume_and_token_totals(monkeypatch, capsys):
|
||||
import hermes_cli.main as main_mod
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,40 @@ def test_status_callback_accepts_single_message_argument():
|
|||
)
|
||||
|
||||
|
||||
def test_resolve_model_uses_inference_model_env(monkeypatch):
|
||||
monkeypatch.delenv("HERMES_MODEL", raising=False)
|
||||
monkeypatch.setenv("HERMES_INFERENCE_MODEL", "anthropic/claude-sonnet-4.6")
|
||||
|
||||
assert server._resolve_model() == "anthropic/claude-sonnet-4.6"
|
||||
|
||||
|
||||
def test_startup_runtime_uses_tui_provider_env(monkeypatch):
|
||||
monkeypatch.setenv("HERMES_MODEL", "nous/hermes-test")
|
||||
monkeypatch.setenv("HERMES_TUI_PROVIDER", "nous")
|
||||
monkeypatch.delenv("HERMES_INFERENCE_PROVIDER", raising=False)
|
||||
|
||||
assert server._resolve_startup_runtime() == ("nous/hermes-test", "nous")
|
||||
|
||||
|
||||
def test_startup_runtime_detects_provider_for_model_env(monkeypatch):
|
||||
monkeypatch.setenv("HERMES_MODEL", "sonnet")
|
||||
monkeypatch.delenv("HERMES_TUI_PROVIDER", raising=False)
|
||||
monkeypatch.delenv("HERMES_INFERENCE_PROVIDER", raising=False)
|
||||
monkeypatch.setattr(server, "_load_cfg", lambda: {"model": {"provider": "auto"}})
|
||||
|
||||
def fake_detect(model, current_provider):
|
||||
assert model == "sonnet"
|
||||
assert current_provider == "auto"
|
||||
return "anthropic", "anthropic/claude-sonnet-4.6"
|
||||
|
||||
monkeypatch.setattr("hermes_cli.models.detect_provider_for_model", fake_detect)
|
||||
|
||||
assert server._resolve_startup_runtime() == (
|
||||
"anthropic/claude-sonnet-4.6",
|
||||
"anthropic",
|
||||
)
|
||||
|
||||
|
||||
def _session(agent=None, **extra):
|
||||
return {
|
||||
"agent": agent if agent is not None else types.SimpleNamespace(),
|
||||
|
|
|
|||
|
|
@ -560,7 +560,7 @@ def resolve_skin() -> dict:
|
|||
|
||||
|
||||
def _resolve_model() -> str:
|
||||
env = os.environ.get("HERMES_MODEL", "")
|
||||
env = os.environ.get("HERMES_MODEL", "") or os.environ.get("HERMES_INFERENCE_MODEL", "")
|
||||
if env:
|
||||
return env
|
||||
m = _load_cfg().get("model", "")
|
||||
|
|
@ -571,6 +571,40 @@ def _resolve_model() -> str:
|
|||
return "anthropic/claude-sonnet-4"
|
||||
|
||||
|
||||
def _resolve_startup_runtime() -> tuple[str, str | None]:
|
||||
model = _resolve_model()
|
||||
explicit_provider = (
|
||||
os.environ.get("HERMES_TUI_PROVIDER", "")
|
||||
or os.environ.get("HERMES_INFERENCE_PROVIDER", "")
|
||||
).strip()
|
||||
if explicit_provider:
|
||||
return model, explicit_provider
|
||||
|
||||
explicit_model = (
|
||||
os.environ.get("HERMES_MODEL", "")
|
||||
or os.environ.get("HERMES_INFERENCE_MODEL", "")
|
||||
).strip()
|
||||
if not explicit_model:
|
||||
return model, None
|
||||
|
||||
try:
|
||||
from hermes_cli.models import detect_provider_for_model
|
||||
|
||||
cfg = _load_cfg().get("model") or {}
|
||||
current_provider = (
|
||||
str(cfg.get("provider") or "").strip().lower()
|
||||
if isinstance(cfg, dict)
|
||||
else ""
|
||||
) or "auto"
|
||||
detected = detect_provider_for_model(explicit_model, current_provider)
|
||||
if detected:
|
||||
provider, detected_model = detected
|
||||
return detected_model, provider
|
||||
except Exception:
|
||||
pass
|
||||
return model, None
|
||||
|
||||
|
||||
def _write_config_key(key_path: str, value):
|
||||
cfg = _load_cfg()
|
||||
current = cfg
|
||||
|
|
@ -1277,9 +1311,13 @@ def _make_agent(sid: str, key: str, session_id: str | None = None):
|
|||
|
||||
cfg = _load_cfg()
|
||||
system_prompt = ((cfg.get("agent") or {}).get("system_prompt", "") or "").strip()
|
||||
runtime = resolve_runtime_provider(requested=None)
|
||||
model, requested_provider = _resolve_startup_runtime()
|
||||
runtime = resolve_runtime_provider(
|
||||
requested=requested_provider,
|
||||
target_model=model or None,
|
||||
)
|
||||
return AIAgent(
|
||||
model=_resolve_model(),
|
||||
model=model,
|
||||
provider=runtime.get("provider"),
|
||||
base_url=runtime.get("base_url"),
|
||||
api_key=runtime.get("api_key"),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue