Files
wehub-resource-sync b4fbd6fe9f
Deploy Site / deploy-vercel (push) Has been skipped
Deploy Site / deploy-docs (push) Has been skipped
Build Skills Index / build-index (push) Has been skipped
Build Skills Index / trigger-deploy (push) Waiting to run
CI / Deny unrelated histories (push) Has been skipped
CI / CI timing report (push) Blocked by required conditions
CI / Detect affected areas (push) Successful in 27m35s
CI / OSV scan (push) Failing after 4s
CI / Build&Test Docker image (push) Successful in 9s
CI / Supply-chain scan (push) Has been skipped
CI / Lint Docker scripts (push) Failing after 5m13s
CI / Check contributors (push) Failing after 12m8s
CI / Docs Site (push) Failing after 12m8s
CI / TypeScript (push) Failing after 12m8s
CI / Python lints (push) Failing after 12m9s
CI / Python tests (push) Failing after 12m9s
CI / Check uv.lock (push) Failing after 23m22s
CI / All required checks pass (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 11:56:03 +08:00

136 lines
5.5 KiB
Python

"""CLI helpers for configuring Mixture of Agents."""
from __future__ import annotations
from typing import Any
from hermes_cli.config import load_config, save_config
from hermes_cli.inventory import build_models_payload, load_picker_context
from hermes_cli.moa_config import DEFAULT_MOA_PRESET_NAME, normalize_moa_config
def _prompt_choice(title: str, rows: list[str], default: int = 0) -> int:
try:
from hermes_cli.curses_ui import curses_radiolist
return curses_radiolist(title, rows, selected=default, cancel_returns=default)
except Exception:
for idx, row in enumerate(rows, start=1):
print(f"{idx}. {row}")
raw = input(f"{title} [{default + 1}]: ").strip()
if not raw:
return default
try:
return max(0, min(len(rows) - 1, int(raw) - 1))
except ValueError:
return default
def _model_options() -> list[dict[str, Any]]:
payload = build_models_payload(
load_picker_context(),
include_unconfigured=True,
picker_hints=True,
canonical_order=True,
pricing=True,
capabilities=True,
max_models=200,
)
providers = payload.get("providers") or []
return [p for p in providers if p.get("slug") and p.get("models")]
def _pick_slot(current: dict[str, str] | None = None) -> dict[str, str]:
providers = _model_options()
if not providers:
raise RuntimeError("No configured model providers found. Run `hermes model` first.")
current_provider = (current or {}).get("provider", "")
provider_default = next(
(idx for idx, p in enumerate(providers) if p.get("slug") == current_provider),
0,
)
provider_rows = [f"{p.get('name') or p.get('slug')} ({p.get('slug')})" for p in providers]
provider = providers[_prompt_choice("Select provider", provider_rows, provider_default)]
models = list(provider.get("models") or [])
if not models:
raise RuntimeError(f"Provider {provider.get('slug')} has no selectable models")
current_model = (current or {}).get("model", "")
model_default = models.index(current_model) if current_model in models else 0
model = models[_prompt_choice(f"Select model for {provider.get('slug')}", models, model_default)]
return {"provider": str(provider.get("slug") or ""), "model": str(model)}
def _print_config(config: dict[str, Any]) -> None:
cfg = normalize_moa_config(config.get("moa") if isinstance(config, dict) else {})
print("Mixture of Agents presets")
print(f"Default: {cfg['default_preset']}")
active = cfg.get("active_preset") or "(off)"
print(f"Active in config: {active}")
for name, preset in cfg["presets"].items():
marker = "*" if name == cfg["default_preset"] else " "
print(f"\n{marker} {name}")
print(" Reference models:")
for idx, slot in enumerate(preset["reference_models"], start=1):
print(f" {idx}. {slot['provider']}:{slot['model']}")
agg = preset["aggregator"]
print(f" Aggregator: {agg['provider']}:{agg['model']}")
def cmd_moa(args) -> None:
"""Manage Mixture of Agents model presets."""
cfg = load_config()
sub = getattr(args, "moa_command", None) or "list"
if sub in {"list", "ls"}:
_print_config(cfg)
return
if sub in {"config", "configure"}:
moa = normalize_moa_config(cfg.get("moa") if isinstance(cfg, dict) else {})
preset_name = (getattr(args, "name", None) or moa.get("default_preset") or DEFAULT_MOA_PRESET_NAME).strip()
current = moa["presets"].get(preset_name, moa["presets"][moa["default_preset"]])
print(f"Configure MoA preset: {preset_name}")
print("Pick at least one reference model; choose Done when finished.")
refs: list[dict[str, str]] = []
existing = list(current.get("reference_models") or [])
idx = 0
while True:
base = existing[idx] if idx < len(existing) else None
refs.append(_pick_slot(base))
idx += 1
choice = _prompt_choice("Add another reference model?", ["Add another", "Done"], 1)
if choice == 1:
break
print("Configure aggregator model.")
current = dict(current)
current["reference_models"] = refs
current["aggregator"] = _pick_slot(current.get("aggregator"))
moa["presets"][preset_name] = current
moa.setdefault("default_preset", preset_name)
cfg["moa"] = normalize_moa_config(moa)
save_config(cfg)
print(f"Saved MoA preset: {preset_name}")
_print_config(cfg)
return
if sub == "delete":
moa = normalize_moa_config(cfg.get("moa") if isinstance(cfg, dict) else {})
preset_name = (getattr(args, "name", None) or "").strip()
if not preset_name:
raise SystemExit("Usage: hermes moa delete <name>")
if preset_name not in moa["presets"]:
raise SystemExit(f"Unknown MoA preset: {preset_name}")
if len(moa["presets"]) <= 1:
raise SystemExit("Cannot delete the only MoA preset")
del moa["presets"][preset_name]
if moa["default_preset"] == preset_name:
moa["default_preset"] = next(iter(moa["presets"]))
if moa.get("active_preset") == preset_name:
moa["active_preset"] = ""
cfg["moa"] = normalize_moa_config(moa)
save_config(cfg)
print(f"Deleted MoA preset: {preset_name}")
return
raise SystemExit(f"Unknown moa subcommand: {sub}")