opentui(phase3): launcher integration — HERMES_TUI_ENGINE dual-engine

hermes --tui launches the native OpenTUI engine (Bun) when
HERMES_TUI_ENGINE=opentui (env) or display.tui_engine=opentui (config);
Ink stays the default and the shipping path is untouched.

- _resolve_tui_engine() (env > config > ink); refuses opentui on
  Windows/Termux (no Bun) -> falls back to ink with a notice.
- _make_opentui_argv() -> [bun, src/entry.real.tsx] (no build step).
- _bun_bin() with HERMES_BUN override.
- Branch at top of _make_tui_argv BEFORE _ensure_tui_node (Bun-only host
  must not bootstrap Node).
- Gate _launch_tui NODE_OPTIONS/--max-old-space-size on engine==ink (Bun
  is JSC; the V8 flag errors/ignores).

Verified end-to-end via tmux: real hermes --tui -> Bun -> OpenTUI ->
real Python gateway streamed a real reply. No-flag default still ink.
This commit is contained in:
alt-glitch
2026-06-08 11:11:54 +00:00
parent 24f74eb888
commit 2bd9c9b881
741 changed files with 17733 additions and 79889 deletions
+3 -29
View File
@@ -491,27 +491,15 @@ def _lift_max_output_tokens(entry: Dict[str, Any], result: Dict[str, Any]) -> No
def _get_named_custom_provider(requested_provider: str) -> Optional[Dict[str, Any]]:
requested_norm = _normalize_custom_provider_name(requested_provider or "")
if not requested_norm:
if not requested_norm or requested_norm == "custom":
return None
# Bare "custom" is normally an incomplete spec — the canonical form is
# "custom:<name>" — and is otherwise owned by the model.base_url "bare
# custom" trust path. BUT a user may literally name a ``providers:`` (or
# legacy ``custom_providers:``) entry "custom" (e.g. ``providers.custom``
# pointing at cliproxy). We used to return None here *before* scanning
# config, so such an entry was never matched and resolution fell through to
# the global default (Codex) — the cause of cron jobs with
# ``provider: "custom"`` failing with ``auth_unavailable: providers=codex``.
# Fall through to the config scan instead; if no entry is literally named
# "custom" it still returns None at the end, preserving the trust path.
# Raw names should only map to custom providers when they are not already
# valid built-in providers or aliases. Explicit menu keys like
# ``custom:local`` always target the saved custom provider. Bare "custom"
# is exempt from the shadow check — it is not a built-in to defer to.
# ``custom:local`` always target the saved custom provider.
if requested_norm == "auto":
return None
if requested_norm != "custom" and not requested_norm.startswith("custom:"):
if not requested_norm.startswith("custom:"):
try:
canonical = auth_mod.resolve_provider(requested_norm)
except AuthError:
@@ -646,20 +634,6 @@ def _get_named_custom_provider(requested_provider: str) -> Optional[Dict[str, An
return None
def has_named_custom_provider(requested_provider: str) -> bool:
"""Return True when config defines a custom provider matching the request.
Thin public wrapper around :func:`_get_named_custom_provider` so other
modules (e.g. the cronjob tool) can decide whether a provider name will
actually resolve to a configured ``providers:`` / ``custom_providers:``
entry — without reaching into a private helper or duplicating the scan.
"""
try:
return _get_named_custom_provider(requested_provider) is not None
except Exception:
return False
def _custom_provider_request_overrides(custom_provider: Dict[str, Any]) -> Dict[str, Any]:
extra_body = custom_provider.get("extra_body")
if not isinstance(extra_body, dict) or not extra_body: