Merge branch 'simp/r3-25-F' into simp/integration3
This commit is contained in:
@@ -39,13 +39,11 @@ _ACTION_RESULTS = LateState("_ACTION_RESULTS")
|
||||
def _action_log_dir() -> Path:
|
||||
"""Live ``web_server._ACTION_LOG_DIR`` (a Path value, so not proxied by LateState)."""
|
||||
from hermes_cli.web_server import _ACTION_LOG_DIR
|
||||
|
||||
return _ACTION_LOG_DIR
|
||||
|
||||
|
||||
def _project_root() -> Path:
|
||||
from hermes_cli.web_server import PROJECT_ROOT
|
||||
|
||||
return PROJECT_ROOT
|
||||
|
||||
|
||||
|
||||
@@ -155,7 +155,6 @@ async def get_client_voice_config(profile: Optional[str] = None):
|
||||
Gate: ``voice.client_direct`` in config.yaml (default true).
|
||||
"""
|
||||
from tools.voice_client_config import resolve_client_voice_config
|
||||
|
||||
try:
|
||||
result = await _run_config_scoped(profile, resolve_client_voice_config)
|
||||
except Exception:
|
||||
@@ -323,7 +322,6 @@ async def tts_lease(payload: TTSLeaseRequest, profile: Optional[str] = None):
|
||||
|
||||
def _apply():
|
||||
from tools.tts_tool import acquire_tts_lease, release_tts_lease
|
||||
|
||||
if payload.active:
|
||||
with _config_profile_scope(profile):
|
||||
return acquire_tts_lease(lease)
|
||||
@@ -376,7 +374,6 @@ async def speak_stream_ws(ws: "WebSocket") -> None:
|
||||
def _resolve():
|
||||
from tools.tts_streaming import resolve_streaming_provider
|
||||
from tools.tts_tool import _get_provider, _load_tts_config, _resolve_max_text_length
|
||||
|
||||
with _config_profile_scope(profile):
|
||||
cfg = _load_tts_config()
|
||||
streamer = resolve_streaming_provider(cfg)
|
||||
|
||||
@@ -105,7 +105,6 @@ async def get_schema(profile: Optional[str] = None):
|
||||
async def get_egress_status():
|
||||
"""Dashboard/Desktop-readable egress proxy status and remediation text."""
|
||||
from hermes_cli.proxy_cli import format_status_text
|
||||
|
||||
return {"text": format_status_text()}
|
||||
|
||||
|
||||
@@ -149,6 +148,15 @@ def _provider_card(d, description: str, url, *, is_password: bool, advanced: boo
|
||||
}
|
||||
|
||||
|
||||
_AUTH_TYPE_ENV_VARS = {
|
||||
"aws_sdk": (
|
||||
("AWS_REGION", lambda d, var: f"{d.label} ({var})"),
|
||||
("AWS_PROFILE", lambda d, var: f"{d.label} ({var})"),
|
||||
),
|
||||
"vertex": (("VERTEX_CREDENTIALS_PATH", lambda d, var: f"{d.label} — service account JSON path (or use ADC)"),),
|
||||
}
|
||||
|
||||
|
||||
def _catalog_provider_env_metadata() -> dict:
|
||||
"""Map provider env vars -> desktop card metadata, derived from the catalog.
|
||||
|
||||
@@ -196,19 +204,11 @@ def _catalog_provider_env_metadata() -> dict:
|
||||
# the Keys tab: AWS-SDK providers (Bedrock) authenticate via the AWS
|
||||
# credential chain, Vertex via OAuth2 (service-account JSON path or
|
||||
# ADC — a path, not a secret). Tag their env vars to the card.
|
||||
if d.auth_type == "aws_sdk":
|
||||
for aws_var in ("AWS_REGION", "AWS_PROFILE"):
|
||||
existing = meta.get(aws_var, {})
|
||||
meta[aws_var] = _provider_card(
|
||||
d, existing.get("description") or f"{d.label} ({aws_var})", existing.get("url"),
|
||||
is_password=False, advanced=existing.get("advanced", True),
|
||||
)
|
||||
if d.auth_type == "vertex":
|
||||
existing = meta.get("VERTEX_CREDENTIALS_PATH", {})
|
||||
meta["VERTEX_CREDENTIALS_PATH"] = _provider_card(
|
||||
d,
|
||||
existing.get("description") or f"{d.label} — service account JSON path (or use ADC)",
|
||||
existing.get("url"), is_password=False, advanced=existing.get("advanced", True),
|
||||
for var, describe in _AUTH_TYPE_ENV_VARS.get(d.auth_type, ()):
|
||||
existing = meta.get(var, {})
|
||||
meta[var] = _provider_card(
|
||||
d, existing.get("description") or describe(d, var), existing.get("url"),
|
||||
is_password=False, advanced=existing.get("advanced", True),
|
||||
)
|
||||
return meta
|
||||
|
||||
@@ -352,6 +352,19 @@ def _config_api_key_is_env_ref(endpoint_id: str) -> bool:
|
||||
return bool(isinstance(raw_key, str) and re.search(r"\$\{[^}]+\}", raw_key))
|
||||
|
||||
|
||||
def _endpoint_row(
|
||||
endpoint_id: str, name: str, base_url: str, model: str, models: List[str], context_length,
|
||||
discover_models: bool, key_entry: Dict[str, Any], is_current: bool, source: str,
|
||||
) -> Dict[str, Any]:
|
||||
has_api_key, api_key_preview = _api_key_display(key_entry)
|
||||
return {
|
||||
"id": endpoint_id, "name": name, "base_url": base_url, "model": model, "models": models,
|
||||
"context_length": context_length, "discover_models": discover_models,
|
||||
"has_api_key": has_api_key, "api_key_preview": api_key_preview,
|
||||
"is_current": is_current, "source": source,
|
||||
}
|
||||
|
||||
|
||||
def _custom_endpoint_response(cfg: Dict[str, Any]) -> Dict[str, Any]:
|
||||
model_cfg = cfg.get("model", {}) if isinstance(cfg.get("model"), dict) else {}
|
||||
current_provider = str(model_cfg.get("provider", "") or "")
|
||||
@@ -369,26 +382,18 @@ def _custom_endpoint_response(cfg: Dict[str, Any]) -> Dict[str, Any]:
|
||||
continue
|
||||
endpoint_id = str(provider_id)
|
||||
models = _models_from_custom_endpoint_entry(raw_entry)
|
||||
endpoint_model = str(raw_entry.get("model") or raw_entry.get("default_model") or (models[0] if models else ""))
|
||||
has_api_key, api_key_preview = _api_key_display(raw_entry)
|
||||
endpoints.append({
|
||||
"id": endpoint_id, "name": str(raw_entry.get("name") or endpoint_id),
|
||||
"base_url": base_url, "model": endpoint_model, "models": models,
|
||||
"context_length": raw_entry.get("context_length"),
|
||||
"discover_models": bool(raw_entry.get("discover_models", True)),
|
||||
"has_api_key": has_api_key, "api_key_preview": api_key_preview,
|
||||
"is_current": endpoint_id == current_provider, "source": "providers",
|
||||
})
|
||||
endpoints.append(_endpoint_row(
|
||||
endpoint_id, str(raw_entry.get("name") or endpoint_id), base_url,
|
||||
str(raw_entry.get("model") or raw_entry.get("default_model") or (models[0] if models else "")),
|
||||
models, raw_entry.get("context_length"), bool(raw_entry.get("discover_models", True)),
|
||||
raw_entry, endpoint_id == current_provider, "providers",
|
||||
))
|
||||
|
||||
if current_provider.lower() == "custom" and current_base_url and not any(e["id"] == "custom" for e in endpoints):
|
||||
has_api_key, api_key_preview = _api_key_display(model_cfg)
|
||||
endpoints.insert(0, {
|
||||
"id": "custom", "name": "Custom", "base_url": current_base_url, "model": current_model,
|
||||
"models": [current_model] if current_model else [],
|
||||
"context_length": model_cfg.get("context_length"), "discover_models": True,
|
||||
"has_api_key": has_api_key, "api_key_preview": api_key_preview, "is_current": True,
|
||||
"source": "direct-config",
|
||||
})
|
||||
endpoints.insert(0, _endpoint_row(
|
||||
"custom", "Custom", current_base_url, current_model, [current_model] if current_model else [],
|
||||
model_cfg.get("context_length"), True, model_cfg, True, "direct-config",
|
||||
))
|
||||
|
||||
return {
|
||||
"endpoints": endpoints,
|
||||
|
||||
@@ -124,7 +124,6 @@ def _read_flat_json(provider: ProviderConfigSchema) -> Dict[str, Any]:
|
||||
def _honcho_resolvers():
|
||||
"""Lazily import the Honcho plugin's resolvers (optional plugin)."""
|
||||
from plugins.memory.honcho.client import _host_block, resolve_active_host, resolve_config_path
|
||||
|
||||
return resolve_active_host, resolve_config_path, _host_block
|
||||
|
||||
|
||||
@@ -434,40 +433,28 @@ def _install_memory_provider_external_dependencies(
|
||||
check_cmd = dep.get("check") or ""
|
||||
install_cmd = dep.get("install") or ""
|
||||
|
||||
def _check(status_of) -> bool:
|
||||
"""Run the check command; append its result; True when it passed."""
|
||||
def _run(kind: str, command: str, status_of, **kwargs) -> Optional[int]:
|
||||
"""Run a setup command, append its result row; returncode or None on spawn failure."""
|
||||
try:
|
||||
check = _run_setup_command(shlex.split(check_cmd), display=check_cmd, timeout=20)
|
||||
completed = _run_setup_command(command if kwargs.get("shell") else shlex.split(command), display=command, **kwargs)
|
||||
except Exception as exc:
|
||||
results.append(_command_result(kind="external_check", name=name, status=status_of(None), command=check_cmd, error=str(exc)))
|
||||
return False
|
||||
ok = check.returncode == 0
|
||||
results.append(_command_result(kind="external_check", name=name, status=status_of(ok), command=check_cmd, completed=check))
|
||||
return ok
|
||||
results.append(_command_result(kind=kind, name=name, status=status_of(None), command=command, error=str(exc)))
|
||||
return None
|
||||
results.append(_command_result(kind=kind, name=name, status=status_of(completed.returncode == 0), command=command, completed=completed))
|
||||
return completed.returncode
|
||||
|
||||
if check_cmd:
|
||||
if _check(lambda ok: "already_installed" if ok else ("missing" if install_cmd else "failed")):
|
||||
continue
|
||||
if not install_cmd:
|
||||
continue
|
||||
|
||||
if install_cmd:
|
||||
try:
|
||||
install = _run_setup_command(install_cmd, display=install_cmd, shell=True, timeout=300)
|
||||
except Exception as exc:
|
||||
results.append(_command_result(kind="external_install", name=name, status="failed", command=install_cmd, error=str(exc)))
|
||||
continue
|
||||
|
||||
results.append(
|
||||
_command_result(
|
||||
kind="external_install", name=name,
|
||||
status="installed" if install.returncode == 0 else "failed",
|
||||
command=install_cmd, completed=install,
|
||||
)
|
||||
)
|
||||
|
||||
if check_cmd and install.returncode == 0:
|
||||
_check(lambda ok: "verified" if ok else "failed")
|
||||
# Check first: "already_installed" short-circuits; a failed check is
|
||||
# "missing" when an install step can fix it, "failed" otherwise.
|
||||
if check_cmd and _run(
|
||||
"external_check", check_cmd,
|
||||
lambda ok: "already_installed" if ok else ("missing" if install_cmd else "failed"), timeout=20,
|
||||
) == 0:
|
||||
continue
|
||||
if not install_cmd:
|
||||
continue
|
||||
rc = _run("external_install", install_cmd, lambda ok: "installed" if ok else "failed", shell=True, timeout=300)
|
||||
if check_cmd and rc == 0:
|
||||
_run("external_check", check_cmd, lambda ok: "verified" if ok else "failed", timeout=20)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@@ -107,6 +107,14 @@ def _start_poller(target, sid: str) -> None:
|
||||
threading.Thread(target=target, args=(sid,), daemon=True, name=f"oauth-poll-{sid[:6]}").start()
|
||||
|
||||
|
||||
def _device_code_started(sid: str, user_code, verification_url, expires_in: int, poll_interval: int) -> Dict[str, Any]:
|
||||
"""The /start response shape shared by every device-code flow."""
|
||||
return {
|
||||
"session_id": sid, "flow": "device_code", "user_code": user_code,
|
||||
"verification_url": verification_url, "expires_in": expires_in, "poll_interval": poll_interval,
|
||||
}
|
||||
|
||||
|
||||
def _codex_full_login_worker(session_id: str) -> None:
|
||||
"""Run the complete OpenAI Codex device-code flow.
|
||||
|
||||
@@ -232,63 +240,46 @@ def _codex_full_login_worker(session_id: str) -> None:
|
||||
s["error_message"] = str(e)
|
||||
|
||||
|
||||
# Hand-written status card shapes per provider id: (hauth getter name, shaper).
|
||||
_OMIT: Any = object()
|
||||
|
||||
|
||||
def _status_card(
|
||||
raw: dict, source, source_label, token_preview, expires_at, has_refresh_token, last_refresh=_OMIT
|
||||
) -> Dict[str, Any]:
|
||||
card = {
|
||||
"logged_in": bool(raw.get("logged_in")), "source": source, "source_label": source_label,
|
||||
"token_preview": token_preview, "expires_at": expires_at, "has_refresh_token": has_refresh_token,
|
||||
}
|
||||
if last_refresh is not _OMIT:
|
||||
card["last_refresh"] = last_refresh
|
||||
return card
|
||||
|
||||
|
||||
# Hand-written status cards per provider id: (hauth getter name, raw -> card).
|
||||
# Providers absent here fall through to the slug-driven ``get_auth_status``.
|
||||
def _nous_status(raw):
|
||||
# Refresh-free snapshot so listing providers never performs an OAuth refresh.
|
||||
return {
|
||||
"logged_in": bool(raw.get("logged_in")), "source": "nous_portal",
|
||||
"source_label": raw.get("portal_base_url") or "Nous Portal",
|
||||
"token_preview": _truncate_token(raw.get("access_token")),
|
||||
"expires_at": raw.get("access_expires_at"),
|
||||
"has_refresh_token": bool(raw.get("has_refresh_token")),
|
||||
}
|
||||
|
||||
|
||||
def _codex_status(raw):
|
||||
return {
|
||||
"logged_in": bool(raw.get("logged_in")), "source": raw.get("source") or "openai_codex",
|
||||
"source_label": raw.get("auth_mode") or "OpenAI Codex",
|
||||
"token_preview": _truncate_token(raw.get("api_key")), "expires_at": None,
|
||||
"has_refresh_token": False, "last_refresh": raw.get("last_refresh"),
|
||||
}
|
||||
|
||||
|
||||
def _qwen_status(raw):
|
||||
return {
|
||||
"logged_in": bool(raw.get("logged_in")), "source": "qwen_cli",
|
||||
"source_label": raw.get("auth_store_path") or "Qwen CLI",
|
||||
"token_preview": _truncate_token(raw.get("access_token")),
|
||||
"expires_at": raw.get("expires_at"),
|
||||
"has_refresh_token": bool(raw.get("has_refresh_token")),
|
||||
}
|
||||
|
||||
|
||||
def _minimax_status(raw):
|
||||
return {
|
||||
"logged_in": bool(raw.get("logged_in")), "source": "minimax_oauth",
|
||||
"source_label": f"MiniMax ({raw.get('region', 'global')})", "token_preview": None,
|
||||
"expires_at": raw.get("expires_at"), "has_refresh_token": True,
|
||||
}
|
||||
|
||||
|
||||
def _xai_status(raw):
|
||||
# source_label is a human-readable origin (auth-store path / credential
|
||||
# source), not the internal auth_mode string ("oauth_pkce").
|
||||
return {
|
||||
"logged_in": bool(raw.get("logged_in")), "source": raw.get("source") or "xai_oauth",
|
||||
"source_label": raw.get("auth_store") or raw.get("source") or "xAI Grok OAuth",
|
||||
"token_preview": _truncate_token(raw.get("api_key")), "expires_at": None,
|
||||
"has_refresh_token": True, "last_refresh": raw.get("last_refresh"),
|
||||
}
|
||||
|
||||
|
||||
# nous: refresh-free local snapshot so listing providers never performs an OAuth
|
||||
# refresh. xai: source_label is a human-readable origin (auth-store path /
|
||||
# credential source), not the internal auth_mode string ("oauth_pkce").
|
||||
_PROVIDER_STATUS: Dict[str, tuple[str, Callable[[dict], dict]]] = {
|
||||
"nous": ("get_nous_auth_status_local", _nous_status),
|
||||
"openai-codex": ("get_codex_auth_status", _codex_status),
|
||||
"qwen-oauth": ("get_qwen_auth_status", _qwen_status),
|
||||
"minimax-oauth": ("get_minimax_oauth_auth_status", _minimax_status),
|
||||
"xai-oauth": ("get_xai_oauth_auth_status", _xai_status),
|
||||
"nous": ("get_nous_auth_status_local", lambda r: _status_card(
|
||||
r, "nous_portal", r.get("portal_base_url") or "Nous Portal",
|
||||
_truncate_token(r.get("access_token")), r.get("access_expires_at"), bool(r.get("has_refresh_token")),
|
||||
)),
|
||||
"openai-codex": ("get_codex_auth_status", lambda r: _status_card(
|
||||
r, r.get("source") or "openai_codex", r.get("auth_mode") or "OpenAI Codex",
|
||||
_truncate_token(r.get("api_key")), None, False, r.get("last_refresh"),
|
||||
)),
|
||||
"qwen-oauth": ("get_qwen_auth_status", lambda r: _status_card(
|
||||
r, "qwen_cli", r.get("auth_store_path") or "Qwen CLI",
|
||||
_truncate_token(r.get("access_token")), r.get("expires_at"), bool(r.get("has_refresh_token")),
|
||||
)),
|
||||
"minimax-oauth": ("get_minimax_oauth_auth_status", lambda r: _status_card(
|
||||
r, "minimax_oauth", f"MiniMax ({r.get('region', 'global')})", None, r.get("expires_at"), True,
|
||||
)),
|
||||
"xai-oauth": ("get_xai_oauth_auth_status", lambda r: _status_card(
|
||||
r, r.get("source") or "xai_oauth", r.get("auth_store") or r.get("source") or "xAI Grok OAuth",
|
||||
_truncate_token(r.get("api_key")), None, True, r.get("last_refresh"),
|
||||
)),
|
||||
}
|
||||
|
||||
|
||||
@@ -310,21 +301,15 @@ def _resolve_provider_status(provider_id: str, status_fn) -> Dict[str, Any]:
|
||||
# a new OAuth/account provider plugin never renders permanently logged-out.
|
||||
raw = hauth.get_auth_status(provider_id)
|
||||
if isinstance(raw, dict) and "logged_in" in raw:
|
||||
return {
|
||||
"logged_in": bool(raw.get("logged_in")),
|
||||
"source": raw.get("source") or raw.get("provider") or provider_id,
|
||||
"source_label": (
|
||||
raw.get("source_label")
|
||||
or raw.get("auth_store")
|
||||
or raw.get("auth_store_path")
|
||||
or raw.get("base_url")
|
||||
or raw.get("name")
|
||||
or ""
|
||||
),
|
||||
"token_preview": _truncate_token(raw.get("access_token") or raw.get("api_key")),
|
||||
"expires_at": raw.get("expires_at") or raw.get("access_expires_at"),
|
||||
"has_refresh_token": bool(raw.get("has_refresh_token")),
|
||||
}
|
||||
return _status_card(
|
||||
raw,
|
||||
raw.get("source") or raw.get("provider") or provider_id,
|
||||
raw.get("source_label") or raw.get("auth_store") or raw.get("auth_store_path")
|
||||
or raw.get("base_url") or raw.get("name") or "",
|
||||
_truncate_token(raw.get("access_token") or raw.get("api_key")),
|
||||
raw.get("expires_at") or raw.get("access_expires_at"),
|
||||
bool(raw.get("has_refresh_token")),
|
||||
)
|
||||
except Exception as e:
|
||||
return {"logged_in": False, "error": str(e)}
|
||||
return {"logged_in": False}
|
||||
@@ -359,11 +344,10 @@ async def _start_nous_device_code(profile: Optional[str]) -> Dict[str, Any]:
|
||||
client_id=client_id, scope=effective_scope,
|
||||
)
|
||||
_start_poller(_nous_poller, sid)
|
||||
return {
|
||||
"session_id": sid, "flow": "device_code", "user_code": str(device_data["user_code"]),
|
||||
"verification_url": str(device_data["verification_uri_complete"]),
|
||||
"expires_in": int(device_data["expires_in"]), "poll_interval": int(device_data["interval"]),
|
||||
}
|
||||
return _device_code_started(
|
||||
sid, str(device_data["user_code"]), str(device_data["verification_uri_complete"]),
|
||||
int(device_data["expires_in"]), int(device_data["interval"]),
|
||||
)
|
||||
|
||||
|
||||
async def _start_codex_device_code(profile: Optional[str]) -> Dict[str, Any]:
|
||||
@@ -385,11 +369,9 @@ async def _start_codex_device_code(profile: Optional[str]) -> Dict[str, Any]:
|
||||
raise HTTPException(status_code=500, detail=s.get("error_message") or "device-auth failed")
|
||||
if not s.get("user_code"):
|
||||
raise HTTPException(status_code=504, detail="device-auth timed out before returning a user code")
|
||||
return {
|
||||
"session_id": sid, "flow": "device_code", "user_code": s["user_code"],
|
||||
"verification_url": s["verification_url"], "expires_in": int(s.get("expires_in") or 900),
|
||||
"poll_interval": int(s.get("interval") or 5),
|
||||
}
|
||||
return _device_code_started(
|
||||
sid, s["user_code"], s["verification_url"], int(s.get("expires_in") or 900), int(s.get("interval") or 5)
|
||||
)
|
||||
|
||||
|
||||
async def _start_minimax_device_code(profile: Optional[str]) -> Dict[str, Any]:
|
||||
@@ -433,11 +415,10 @@ async def _start_minimax_device_code(profile: Optional[str]) -> Dict[str, Any]:
|
||||
expires_in_seconds = expired_in_raw
|
||||
sess["expires_at"] = expires_at_ts
|
||||
_start_poller(_minimax_poller, sid)
|
||||
return {
|
||||
"session_id": sid, "flow": "device_code", "user_code": str(device_data["user_code"]),
|
||||
"verification_url": str(device_data["verification_uri"]), "expires_in": expires_in_seconds,
|
||||
"poll_interval": max(2, (sess["interval_ms"] or 2000) // 1000),
|
||||
}
|
||||
return _device_code_started(
|
||||
sid, str(device_data["user_code"]), str(device_data["verification_uri"]), expires_in_seconds,
|
||||
max(2, (sess["interval_ms"] or 2000) // 1000),
|
||||
)
|
||||
|
||||
|
||||
async def _start_xai_device_code(profile: Optional[str]) -> Dict[str, Any]:
|
||||
@@ -455,11 +436,11 @@ async def _start_xai_device_code(profile: Optional[str]) -> Dict[str, Any]:
|
||||
expires_at=time.time() + int(device_data["expires_in"]),
|
||||
)
|
||||
_start_poller(_xai_device_poller, sid)
|
||||
return {
|
||||
"session_id": sid, "flow": "device_code", "user_code": str(device_data["user_code"]),
|
||||
"verification_url": str(device_data.get("verification_uri_complete") or device_data["verification_uri"]),
|
||||
"expires_in": int(device_data["expires_in"]), "poll_interval": int(device_data["interval"]),
|
||||
}
|
||||
return _device_code_started(
|
||||
sid, str(device_data["user_code"]),
|
||||
str(device_data.get("verification_uri_complete") or device_data["verification_uri"]),
|
||||
int(device_data["expires_in"]), int(device_data["interval"]),
|
||||
)
|
||||
|
||||
|
||||
_DEVICE_CODE_STARTERS = {
|
||||
|
||||
Reference in New Issue
Block a user