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:
@@ -136,101 +136,6 @@ class TestPartialStreamStubFinishReason:
|
||||
assert "write_file" in content
|
||||
|
||||
|
||||
# ── Clean stream-end mid-tool-call (no exception, no finish_reason) ─────────
|
||||
|
||||
class TestCleanStreamEndMidToolCall:
|
||||
"""The upstream closes the SSE stream cleanly after delivering a tool
|
||||
name + the opening '{' of its arguments — NO exception, NO finish_reason,
|
||||
NO [DONE]. Observed live on NVIDIA Nemotron Ultra via the Nous dedicated
|
||||
endpoint: it stalls/drops during large tool-arg generation.
|
||||
|
||||
The mock-builder must NOT stamp this as finish_reason='length' (which
|
||||
routes it through the max_tokens-boost truncation path and finally
|
||||
reports the misleading 'Response truncated due to output length limit').
|
||||
It must route through the partial-stream-stub path so the loop reports
|
||||
an honest mid-tool-call drop and asks the model to chunk its output.
|
||||
"""
|
||||
|
||||
@patch("run_agent.AIAgent._create_request_openai_client")
|
||||
@patch("run_agent.AIAgent._close_request_openai_client")
|
||||
def test_no_finish_reason_partial_tool_args_routes_to_stub(
|
||||
self, _mock_close, mock_create, monkeypatch,
|
||||
):
|
||||
def _clean_ending_stream():
|
||||
# Reasoning + tool name + the lone opening brace, then the
|
||||
# generator simply RETURNS (StopIteration) — no raise, no
|
||||
# finish_reason chunk, no [DONE].
|
||||
yield _make_stream_chunk(content="\n")
|
||||
yield _make_stream_chunk(tool_calls=[
|
||||
_make_tool_call_delta(index=0, tc_id="call_x", name="execute_code"),
|
||||
])
|
||||
yield _make_stream_chunk(tool_calls=[
|
||||
_make_tool_call_delta(index=0, arguments="{"),
|
||||
])
|
||||
# falls off the end — clean close, no terminator
|
||||
|
||||
mock_client = MagicMock()
|
||||
mock_client.chat.completions.create.side_effect = (
|
||||
lambda *a, **kw: _clean_ending_stream()
|
||||
)
|
||||
mock_create.return_value = mock_client
|
||||
|
||||
agent = _make_agent()
|
||||
agent._fire_stream_delta = lambda text: None
|
||||
|
||||
response = agent._interruptible_streaming_api_call({})
|
||||
|
||||
assert response.id == PARTIAL_STREAM_STUB_ID, (
|
||||
"A clean stream-end mid tool-call (no finish_reason) must be "
|
||||
"tagged as a partial-stream stub, not a 'stream-<uuid>' "
|
||||
"truncation — otherwise the loop reports the false 'output "
|
||||
"length limit' error."
|
||||
)
|
||||
assert response.choices[0].finish_reason == FINISH_REASON_LENGTH
|
||||
assert response.choices[0].message.tool_calls is None, (
|
||||
"Incomplete tool args must never auto-execute."
|
||||
)
|
||||
assert getattr(response, "_dropped_tool_names", None) == ["execute_code"]
|
||||
|
||||
@patch("run_agent.AIAgent._create_request_openai_client")
|
||||
@patch("run_agent.AIAgent._close_request_openai_client")
|
||||
def test_real_length_truncation_still_uses_uuid_id(
|
||||
self, _mock_close, mock_create, monkeypatch,
|
||||
):
|
||||
"""Control: when the provider DOES send finish_reason='length' with
|
||||
partial tool args, it is a genuine output cap — keep the existing
|
||||
non-stub behaviour (boost max_tokens and retry)."""
|
||||
|
||||
def _capped_stream():
|
||||
yield _make_stream_chunk(tool_calls=[
|
||||
_make_tool_call_delta(index=0, tc_id="call_y", name="execute_code"),
|
||||
])
|
||||
yield _make_stream_chunk(tool_calls=[
|
||||
_make_tool_call_delta(index=0, arguments="{"),
|
||||
])
|
||||
# Provider explicitly reports the output cap.
|
||||
yield _make_stream_chunk(finish_reason="length")
|
||||
|
||||
mock_client = MagicMock()
|
||||
mock_client.chat.completions.create.side_effect = (
|
||||
lambda *a, **kw: _capped_stream()
|
||||
)
|
||||
mock_create.return_value = mock_client
|
||||
|
||||
agent = _make_agent()
|
||||
agent._fire_stream_delta = lambda text: None
|
||||
|
||||
response = agent._interruptible_streaming_api_call({})
|
||||
|
||||
assert response.id != PARTIAL_STREAM_STUB_ID, (
|
||||
"A provider-reported finish_reason='length' is a real output cap "
|
||||
"and must keep the existing truncation path, not the stream-drop "
|
||||
"stub path."
|
||||
)
|
||||
assert response.id.startswith("stream-")
|
||||
assert response.choices[0].finish_reason == FINISH_REASON_LENGTH
|
||||
|
||||
|
||||
# ── Length-continuation prompt branching ──────────────────────────────────
|
||||
|
||||
class TestLengthContinuationPromptBranching:
|
||||
|
||||
Reference in New Issue
Block a user