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
+4 -4
View File
@@ -124,11 +124,11 @@ class TestOpenRouterProfileParity:
def test_reasoning_full_config(self, transport):
rc = {"enabled": True, "effort": "high"}
legacy = transport.build_kwargs(
model="deepseek/deepseek-chat", messages=_msgs(), tools=None,
model="anthropic/claude-sonnet-4.6", messages=_msgs(), tools=None,
provider_profile=get_provider_profile("openrouter"), supports_reasoning=True, reasoning_config=rc,
)
profile = transport.build_kwargs(
model="deepseek/deepseek-chat", messages=_msgs(), tools=None,
model="anthropic/claude-sonnet-4.6", messages=_msgs(), tools=None,
provider_profile=get_provider_profile("openrouter"),
supports_reasoning=True, reasoning_config=rc,
)
@@ -136,11 +136,11 @@ class TestOpenRouterProfileParity:
def test_default_reasoning(self, transport):
legacy = transport.build_kwargs(
model="deepseek/deepseek-chat", messages=_msgs(), tools=None,
model="anthropic/claude-sonnet-4.6", messages=_msgs(), tools=None,
provider_profile=get_provider_profile("openrouter"), supports_reasoning=True,
)
profile = transport.build_kwargs(
model="deepseek/deepseek-chat", messages=_msgs(), tools=None,
model="anthropic/claude-sonnet-4.6", messages=_msgs(), tools=None,
provider_profile=get_provider_profile("openrouter"),
supports_reasoning=True,
)
-186
View File
@@ -169,77 +169,6 @@ class TestOpenRouterProfile:
)
assert eb["reasoning"] == {"enabled": False}
def test_reasoning_disable_omitted_for_mandatory_anthropic(self):
"""Reasoning-mandatory Anthropic models (4.6+/fable) reject any disable
form: OpenRouter translates ``reasoning: {enabled: false}`` into
Anthropic's ``thinking: {type: disabled}``, which 400s. The profile must
omit ``reasoning`` so the model falls back to adaptive thinking instead.
"""
p = get_provider_profile("openrouter")
for model in (
"anthropic/claude-fable-5", # new named model
"anthropic/claude-some-future-7", # unknown → default mandatory
"anthropic/claude-opus-4.8",
"anthropic/claude-opus-4.6",
):
for cfg in ({"enabled": False}, {"effort": "none"}):
eb, _ = p.build_api_kwargs_extras(
reasoning_config=cfg,
supports_reasoning=True,
model=model,
)
assert "reasoning" not in eb, (model, cfg, eb)
def test_reasoning_disable_kept_for_legacy_anthropic(self):
"""Older Anthropic models still accept an explicit disable form, so the
profile must keep forwarding it."""
p = get_provider_profile("openrouter")
for model in (
"anthropic/claude-3.7-sonnet",
"anthropic/claude-opus-4.5",
"anthropic/claude-sonnet-4.5",
):
eb, _ = p.build_api_kwargs_extras(
reasoning_config={"enabled": False},
supports_reasoning=True,
model=model,
)
assert eb["reasoning"] == {"enabled": False}, (model, eb)
def test_reasoning_disable_kept_for_non_anthropic(self):
"""Non-Anthropic models (DeepSeek, Qwen, …) disable reasoning fine; the
Anthropic-mandatory guard must not touch them."""
p = get_provider_profile("openrouter")
for model in ("deepseek/deepseek-chat", "qwen/qwen3-max", "openai/gpt-5.4"):
eb, _ = p.build_api_kwargs_extras(
reasoning_config={"enabled": False},
supports_reasoning=True,
model=model,
)
assert eb["reasoning"] == {"enabled": False}, (model, eb)
def test_reasoning_omitted_for_mandatory_anthropic_even_when_enabled(self):
"""Reasoning-mandatory Anthropic models (4.6+/fable) use adaptive
thinking — OpenRouter ignores reasoning.effort for them, and sending any
reasoning field makes OpenRouter emit thinking.type.disabled on
tool-continuation turns (whose assistant tool_calls carry no thinking
block), 400ing every turn after the first tool call. The profile must
omit reasoning entirely so the model defaults to adaptive.
"""
p = get_provider_profile("openrouter")
for cfg in (
{"enabled": True, "effort": "medium"},
{"enabled": True, "effort": "xhigh"},
{"effort": "high"},
{"enabled": True},
):
eb, _ = p.build_api_kwargs_extras(
reasoning_config=cfg,
supports_reasoning=True,
model="anthropic/claude-fable-5",
)
assert "reasoning" not in eb, (cfg, eb)
def test_default_reasoning(self):
p = get_provider_profile("openrouter")
eb, _ = p.build_api_kwargs_extras(supports_reasoning=True)
@@ -291,121 +220,6 @@ class TestOpenRouterProfile:
assert eb["reasoning"] == {"enabled": True, "effort": "high"}
assert tl["extra_headers"]["x-grok-conv-id"] == "sess-123"
# --- reasoning-mandatory Anthropic effort → top-level verbosity (#43432) ---
#
# These models (Claude 4.6+ / fable / mythos-class) ignore
# ``reasoning.effort`` and use adaptive thinking. OpenRouter honors the
# requested effort on the top-level ``verbosity`` field instead (maps to
# Anthropic ``output_config.effort``). The profile must route the existing
# ``reasoning_config["effort"]`` there while still NEVER emitting a
# ``reasoning`` field (which would 400 — see #42991). Gate every fixture on
# the real predicate so this stays a behavior contract, not a name snapshot.
@staticmethod
def _is_mandatory(model):
import inspect
p = get_provider_profile("openrouter")
mod = inspect.getmodule(type(p))
return mod._anthropic_reasoning_is_mandatory(model)
def test_mandatory_anthropic_effort_routes_to_verbosity(self):
"""effort set + reasoning enabled → top-level verbosity == effort,
and NO reasoning field in extra_body.
Covers the full real config range produced by
``hermes_constants.parse_reasoning_effort`` —
``VALID_REASONING_EFFORTS = (minimal, low, medium, high, xhigh)``.
"""
p = get_provider_profile("openrouter")
model = "anthropic/claude-fable-5"
assert self._is_mandatory(model) # fixture really is mandatory
for effort in ("minimal", "low", "medium", "high", "xhigh"):
eb, tl = p.build_api_kwargs_extras(
reasoning_config={"enabled": True, "effort": effort},
supports_reasoning=True,
model=model,
)
assert tl["verbosity"] == effort, (effort, tl)
assert "reasoning" not in eb, (effort, eb)
def test_mandatory_anthropic_effort_without_enabled_key_routes(self):
"""effort present without an explicit ``enabled`` key still routes to
verbosity (enabled defaults to True)."""
p = get_provider_profile("openrouter")
eb, tl = p.build_api_kwargs_extras(
reasoning_config={"effort": "xhigh"},
supports_reasoning=True,
model="anthropic/claude-fable-5",
)
assert tl["verbosity"] == "xhigh"
assert "reasoning" not in eb
def test_mandatory_anthropic_verbosity_is_value_agnostic_passthrough(self):
"""The mapping passes the effort value through verbatim — it must NOT
clamp or whitelist. ``xhigh`` is a real config value; ``max`` is not
producible by ``parse_reasoning_effort`` today but OpenRouter accepts it
for Claude (live-proven in #43432), so a forward value must survive
rather than be silently dropped. The OpenAI SDK type only literals
``low|medium|high`` but it's a TypedDict (no runtime validation), so the
extended scale reaches the wire untouched."""
p = get_provider_profile("openrouter")
for effort in ("xhigh", "max"):
_, tl = p.build_api_kwargs_extras(
reasoning_config={"enabled": True, "effort": effort},
supports_reasoning=True,
model="anthropic/claude-fable-5",
)
assert tl["verbosity"] == effort
def test_mandatory_anthropic_no_verbosity_when_effort_absent(self):
"""No effort / none / disabled → no verbosity emitted, so the model
keeps its own adaptive default. Still no reasoning field."""
p = get_provider_profile("openrouter")
model = "anthropic/claude-fable-5"
for cfg in (
None,
{},
{"enabled": True},
{"effort": "none"},
{"enabled": True, "effort": "none"},
{"enabled": False, "effort": "high"}, # explicitly disabled wins
):
eb, tl = p.build_api_kwargs_extras(
reasoning_config=cfg,
supports_reasoning=True,
model=model,
)
assert "verbosity" not in tl, (cfg, tl)
assert "reasoning" not in eb, (cfg, eb)
def test_non_mandatory_reasoning_model_unchanged_no_verbosity(self):
"""Non-mandatory reasoning models (DeepSeek, Qwen, GPT) keep getting
``reasoning`` in extra_body and never get a ``verbosity`` field — the
new path must not touch them."""
p = get_provider_profile("openrouter")
for model in ("deepseek/deepseek-chat", "qwen/qwen3-max", "openai/gpt-5.4"):
assert not self._is_mandatory(model) # fixture really is non-mandatory
eb, tl = p.build_api_kwargs_extras(
reasoning_config={"enabled": True, "effort": "high"},
supports_reasoning=True,
model=model,
)
assert eb["reasoning"] == {"enabled": True, "effort": "high"}, (model, eb)
assert "verbosity" not in tl, (model, tl)
def test_mandatory_anthropic_verbosity_coexists_with_grok_header(self):
"""A reasoning-mandatory Anthropic model is never a Grok model, but the
top-level dict must remain a single merged dict — verify the verbosity
path doesn't clobber the extra_headers slot used by Grok affinity."""
p = get_provider_profile("openrouter")
# mandatory anthropic + effort → verbosity, no extra_headers
_, tl = p.build_api_kwargs_extras(
reasoning_config={"enabled": True, "effort": "high"},
supports_reasoning=True,
model="anthropic/claude-fable-5",
)
assert tl == {"verbosity": "high"}
class TestNousProfile:
def test_tags(self):
+2 -16
View File
@@ -160,7 +160,7 @@ class TestOpenRouterParity:
"""OpenRouter passes the FULL reasoning_config dict, not just effort."""
rc = {"enabled": True, "effort": "high"}
kw = transport.build_kwargs(
model="deepseek/deepseek-chat",
model="anthropic/claude-sonnet-4.6",
messages=_simple_messages(),
tools=None,
provider_profile=get_provider_profile("openrouter"),
@@ -169,24 +169,10 @@ class TestOpenRouterParity:
)
assert kw["extra_body"]["reasoning"] == rc
def test_reasoning_omitted_for_mandatory_anthropic(self, transport):
"""Adaptive-thinking Anthropic models (4.6+/fable) get NO reasoning
field — sending one makes OpenRouter emit thinking.type.disabled on
tool-replay turns, which the model 400s on."""
kw = transport.build_kwargs(
model="anthropic/claude-sonnet-4.6",
messages=_simple_messages(),
tools=None,
provider_profile=get_provider_profile("openrouter"),
supports_reasoning=True,
reasoning_config={"enabled": True, "effort": "high"},
)
assert "reasoning" not in kw.get("extra_body", {})
def test_default_reasoning_when_no_config(self, transport):
"""When supports_reasoning=True but no config, adds default."""
kw = transport.build_kwargs(
model="deepseek/deepseek-chat",
model="anthropic/claude-sonnet-4.6",
messages=_simple_messages(),
tools=None,
provider_profile=get_provider_profile("openrouter"),