minionsai 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -12,7 +12,12 @@ let shuttingDown = false;
|
|
|
12
12
|
async function main() {
|
|
13
13
|
ensureBundledSkillsLinked();
|
|
14
14
|
closeFrontend = await mountFrontend(app, httpServer);
|
|
15
|
-
|
|
15
|
+
try {
|
|
16
|
+
await adapter.start();
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
console.error('Hermes worker failed to start — UI will load but agent features are unavailable until the worker recovers:', error instanceof Error ? error.message : error);
|
|
20
|
+
}
|
|
16
21
|
httpServer.listen(PORT);
|
|
17
22
|
await once(httpServer, 'listening');
|
|
18
23
|
console.log(`Hermes Agent Mission Control running on http://localhost:${PORT}`);
|
|
@@ -66,19 +66,49 @@ KNOWN_PROVIDER_PREFIXES = {
|
|
|
66
66
|
"kimi",
|
|
67
67
|
"kimi-coding",
|
|
68
68
|
"minimax",
|
|
69
|
+
"minimax-cn",
|
|
69
70
|
"mistral",
|
|
70
71
|
"mistralai",
|
|
71
72
|
"moonshotai",
|
|
72
73
|
"nous",
|
|
74
|
+
"nvidia",
|
|
73
75
|
"ollama",
|
|
74
76
|
"ollama-cloud",
|
|
77
|
+
"opencode-go",
|
|
78
|
+
"opencode-zen",
|
|
75
79
|
"openrouter",
|
|
76
80
|
"qwen",
|
|
77
81
|
"x-ai",
|
|
78
82
|
"xai",
|
|
83
|
+
"xiaomi",
|
|
79
84
|
"z-ai",
|
|
80
85
|
"zai",
|
|
81
86
|
}
|
|
87
|
+
PORTAL_PROVIDERS = {"nous", "opencode-zen", "opencode-go", "nvidia"}
|
|
88
|
+
LOCAL_SERVER_PROVIDERS = {
|
|
89
|
+
"lmstudio",
|
|
90
|
+
"lm-studio",
|
|
91
|
+
"ollama",
|
|
92
|
+
"llamacpp",
|
|
93
|
+
"llama-cpp",
|
|
94
|
+
"vllm",
|
|
95
|
+
"tabby",
|
|
96
|
+
"tabbyapi",
|
|
97
|
+
"koboldcpp",
|
|
98
|
+
"textgen",
|
|
99
|
+
"localai",
|
|
100
|
+
}
|
|
101
|
+
CODEX_BASE_URL = "https://chatgpt.com/backend-api/codex"
|
|
102
|
+
RUNTIME_MANAGED_PROVIDER_PREFIXES = {
|
|
103
|
+
"copilot",
|
|
104
|
+
"copilot-acp",
|
|
105
|
+
"google-gemini-cli",
|
|
106
|
+
"minimax-oauth",
|
|
107
|
+
"nous",
|
|
108
|
+
"openai-codex",
|
|
109
|
+
"qwen-oauth",
|
|
110
|
+
}
|
|
111
|
+
MODEL_RUNTIME_OVERRIDE_KEYS = ("base_url", "api_key", "api", "api_mode")
|
|
82
112
|
|
|
83
113
|
_AGENT_DIR: Path | None = None
|
|
84
114
|
_IMPORTS_READY = False
|
|
@@ -315,15 +345,23 @@ def _set_defaults(request: dict[str, Any]) -> dict[str, Any]:
|
|
|
315
345
|
if isinstance(raw_model, str) and raw_model.strip():
|
|
316
346
|
model_val = raw_model.strip()
|
|
317
347
|
parsed = _parse_provider_model(model_val)
|
|
348
|
+
previous_provider = string_or_none(cfg["model"].get("provider"))
|
|
318
349
|
if parsed:
|
|
319
350
|
provider_hint, bare_model = parsed
|
|
320
351
|
if provider_hint and not _provider_hint_is_available(provider_hint):
|
|
321
352
|
_raise_invalid_provider(provider_hint)
|
|
322
353
|
cfg["model"]["default"] = bare_model
|
|
323
354
|
cfg["model"]["provider"] = provider_hint
|
|
355
|
+
if (
|
|
356
|
+
provider_hint != previous_provider
|
|
357
|
+
or provider_hint in RUNTIME_MANAGED_PROVIDER_PREFIXES
|
|
358
|
+
):
|
|
359
|
+
_clear_model_runtime_overrides(cfg["model"])
|
|
324
360
|
else:
|
|
325
361
|
cfg["model"]["default"] = model_val
|
|
326
362
|
cfg["model"].pop("provider", None)
|
|
363
|
+
if previous_provider and not previous_provider.startswith("custom"):
|
|
364
|
+
_clear_model_runtime_overrides(cfg["model"])
|
|
327
365
|
|
|
328
366
|
if "reasoningEffort" in request:
|
|
329
367
|
normalized = _normalize_reasoning(request["reasoningEffort"])
|
|
@@ -339,6 +377,11 @@ def _set_defaults(request: dict[str, Any]) -> dict[str, Any]:
|
|
|
339
377
|
return _defaults_from_config(cfg)
|
|
340
378
|
|
|
341
379
|
|
|
380
|
+
def _clear_model_runtime_overrides(model_cfg: dict[str, Any]) -> None:
|
|
381
|
+
for key in MODEL_RUNTIME_OVERRIDE_KEYS:
|
|
382
|
+
model_cfg.pop(key, None)
|
|
383
|
+
|
|
384
|
+
|
|
342
385
|
def _defaults_from_config(cfg: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
343
386
|
cfg = cfg if cfg is not None else _load_config()
|
|
344
387
|
model_cfg = _model_section(cfg)
|
|
@@ -411,11 +454,39 @@ def _custom_provider_models(entry: dict[str, Any]) -> list[str]:
|
|
|
411
454
|
|
|
412
455
|
def _parse_provider_model(raw: str) -> tuple[str, str] | None:
|
|
413
456
|
if raw.startswith("@") and ":" in raw:
|
|
414
|
-
|
|
457
|
+
inner = raw[1:]
|
|
458
|
+
provider, model = inner.rsplit(":", 1)
|
|
459
|
+
if provider.startswith("custom:") and provider.count(":") >= 2:
|
|
460
|
+
slug_rest = provider[len("custom:"):]
|
|
461
|
+
if not _custom_slug_rest_looks_like_host_port(slug_rest):
|
|
462
|
+
provider, extra = provider.rsplit(":", 1)
|
|
463
|
+
model = f"{extra}:{model}"
|
|
464
|
+
elif provider not in KNOWN_PROVIDER_PREFIXES and not provider.startswith("custom:"):
|
|
465
|
+
provider, model = inner.split(":", 1)
|
|
415
466
|
return provider, model
|
|
416
467
|
return None
|
|
417
468
|
|
|
418
469
|
|
|
470
|
+
def _custom_slug_rest_looks_like_host_port(rest: str) -> bool:
|
|
471
|
+
rest = str(rest or "").strip()
|
|
472
|
+
if ":" not in rest:
|
|
473
|
+
return False
|
|
474
|
+
host, port_s = rest.rsplit(":", 1)
|
|
475
|
+
if not host or ":" in host or not port_s.isdigit():
|
|
476
|
+
return False
|
|
477
|
+
if not (1 <= int(port_s) <= 65535):
|
|
478
|
+
return False
|
|
479
|
+
try:
|
|
480
|
+
import ipaddress
|
|
481
|
+
|
|
482
|
+
ipaddress.ip_address(host)
|
|
483
|
+
return True
|
|
484
|
+
except ValueError:
|
|
485
|
+
pass
|
|
486
|
+
host_l = host.lower()
|
|
487
|
+
return host_l == "localhost" or "." in host
|
|
488
|
+
|
|
489
|
+
|
|
419
490
|
def _provider_hint_is_available(provider: str) -> bool:
|
|
420
491
|
if provider.startswith("custom:"):
|
|
421
492
|
return True
|
|
@@ -495,6 +566,36 @@ def _groups_have_model(groups: dict[str, list[dict[str, Any]]], model_id: str) -
|
|
|
495
566
|
)
|
|
496
567
|
|
|
497
568
|
|
|
569
|
+
def _is_local_server_provider(provider: str | None) -> bool:
|
|
570
|
+
provider_l = str(provider or "").strip().lower()
|
|
571
|
+
if provider_l in LOCAL_SERVER_PROVIDERS:
|
|
572
|
+
return True
|
|
573
|
+
if provider_l.startswith("custom:"):
|
|
574
|
+
return provider_l.removeprefix("custom:") in LOCAL_SERVER_PROVIDERS
|
|
575
|
+
return False
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def _base_url_points_at_local_server(base_url: str | None) -> bool:
|
|
579
|
+
if not base_url:
|
|
580
|
+
return False
|
|
581
|
+
try:
|
|
582
|
+
import ipaddress
|
|
583
|
+
from urllib.parse import urlparse
|
|
584
|
+
|
|
585
|
+
host = (urlparse(base_url).hostname or "").lower()
|
|
586
|
+
if host in {"localhost", "ip6-localhost", "ip6-loopback"}:
|
|
587
|
+
return True
|
|
588
|
+
if not host:
|
|
589
|
+
return False
|
|
590
|
+
try:
|
|
591
|
+
addr = ipaddress.ip_address(host)
|
|
592
|
+
except ValueError:
|
|
593
|
+
return False
|
|
594
|
+
return addr.is_loopback or addr.is_private or addr.is_link_local
|
|
595
|
+
except Exception:
|
|
596
|
+
return False
|
|
597
|
+
|
|
598
|
+
|
|
498
599
|
def _model_option_id(provider: str | None, model_id: str, active_provider: str | None) -> str:
|
|
499
600
|
if not provider or provider == active_provider:
|
|
500
601
|
return model_id
|
|
@@ -620,6 +721,7 @@ def _resolve_model_provider(requested_model: str | None, cfg: dict[str, Any] | N
|
|
|
620
721
|
model_cfg = _model_section(cfg)
|
|
621
722
|
config_provider = string_or_none(model_cfg.get("provider"))
|
|
622
723
|
config_base_url = string_or_none(model_cfg.get("base_url"))
|
|
724
|
+
config_provider_l = (config_provider or "").lower()
|
|
623
725
|
default_model = string_or_none(model_cfg.get("default"))
|
|
624
726
|
model_id = (requested_model or default_model or "").strip()
|
|
625
727
|
|
|
@@ -643,17 +745,26 @@ def _resolve_model_provider(requested_model: str | None, cfg: dict[str, Any] | N
|
|
|
643
745
|
if "/" in model_id:
|
|
644
746
|
prefix, bare = model_id.split("/", 1)
|
|
645
747
|
prefix_normalized = prefix.lower()
|
|
646
|
-
if
|
|
748
|
+
if config_provider_l == "openrouter":
|
|
647
749
|
return model_id, "openrouter", config_base_url
|
|
648
|
-
if
|
|
649
|
-
return bare, config_provider, config_base_url
|
|
650
|
-
if config_provider in {"nous", "opencode-zen", "opencode-go"}:
|
|
750
|
+
if config_provider_l in PORTAL_PROVIDERS:
|
|
651
751
|
return model_id, config_provider, config_base_url
|
|
752
|
+
if config_provider and prefix_normalized == config_provider_l:
|
|
753
|
+
return bare, config_provider, config_base_url
|
|
754
|
+
if (
|
|
755
|
+
config_provider_l == "openai-codex"
|
|
756
|
+
and (config_base_url or "").rstrip("/") == CODEX_BASE_URL
|
|
757
|
+
and prefix_normalized in KNOWN_PROVIDER_PREFIXES
|
|
758
|
+
and prefix_normalized != config_provider_l
|
|
759
|
+
):
|
|
760
|
+
return model_id, "openrouter", None
|
|
652
761
|
if config_base_url:
|
|
762
|
+
if _is_local_server_provider(config_provider) or _base_url_points_at_local_server(config_base_url):
|
|
763
|
+
return model_id, config_provider, config_base_url
|
|
653
764
|
if prefix_normalized in KNOWN_PROVIDER_PREFIXES:
|
|
654
765
|
return bare, config_provider, config_base_url
|
|
655
766
|
return model_id, config_provider, config_base_url
|
|
656
|
-
if prefix_normalized in KNOWN_PROVIDER_PREFIXES and prefix_normalized !=
|
|
767
|
+
if prefix_normalized in KNOWN_PROVIDER_PREFIXES and prefix_normalized != config_provider_l:
|
|
657
768
|
return model_id, "openrouter", None
|
|
658
769
|
|
|
659
770
|
return model_id, config_provider, config_base_url
|
|
@@ -846,11 +957,7 @@ def _sync_session_identity(agent: Any, session_id: str) -> None:
|
|
|
846
957
|
|
|
847
958
|
|
|
848
959
|
def _warm_agent() -> None:
|
|
849
|
-
|
|
850
|
-
session_id="minions-healthcheck",
|
|
851
|
-
requested_model=None,
|
|
852
|
-
reasoning_effort=None,
|
|
853
|
-
)
|
|
960
|
+
_load_config()
|
|
854
961
|
|
|
855
962
|
|
|
856
963
|
def _run_chat(request_id: str, request: dict[str, Any]) -> None:
|