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:
+8
-48
@@ -196,7 +196,7 @@ from agent.tool_dispatch_helpers import (
|
||||
_extract_error_preview,
|
||||
_trajectory_normalize_msg, # noqa: F401 # re-exported for tests that `from run_agent import _trajectory_normalize_msg`
|
||||
)
|
||||
from utils import atomic_json_write, base_url_host_matches, base_url_hostname, is_truthy_value, model_forces_max_completion_tokens
|
||||
from utils import atomic_json_write, base_url_host_matches, base_url_hostname, is_truthy_value
|
||||
|
||||
|
||||
|
||||
@@ -358,7 +358,6 @@ class AIAgent:
|
||||
save_trajectories: bool = False,
|
||||
verbose_logging: bool = False,
|
||||
quiet_mode: bool = False,
|
||||
tool_progress_mode: str = "all",
|
||||
ephemeral_system_prompt: str = None,
|
||||
log_prefix_chars: int = 100,
|
||||
log_prefix: str = "",
|
||||
@@ -376,7 +375,6 @@ class AIAgent:
|
||||
thinking_callback: callable = None,
|
||||
reasoning_callback: callable = None,
|
||||
clarify_callback: callable = None,
|
||||
read_terminal_callback: callable = None,
|
||||
step_callback: callable = None,
|
||||
stream_delta_callback: callable = None,
|
||||
interim_assistant_callback: callable = None,
|
||||
@@ -432,7 +430,6 @@ class AIAgent:
|
||||
save_trajectories=save_trajectories,
|
||||
verbose_logging=verbose_logging,
|
||||
quiet_mode=quiet_mode,
|
||||
tool_progress_mode=tool_progress_mode,
|
||||
ephemeral_system_prompt=ephemeral_system_prompt,
|
||||
log_prefix_chars=log_prefix_chars,
|
||||
log_prefix=log_prefix,
|
||||
@@ -450,7 +447,6 @@ class AIAgent:
|
||||
thinking_callback=thinking_callback,
|
||||
reasoning_callback=reasoning_callback,
|
||||
clarify_callback=clarify_callback,
|
||||
read_terminal_callback=read_terminal_callback,
|
||||
step_callback=step_callback,
|
||||
stream_delta_callback=stream_delta_callback,
|
||||
interim_assistant_callback=interim_assistant_callback,
|
||||
@@ -1253,24 +1249,13 @@ class AIAgent:
|
||||
def _max_tokens_param(self, value: int) -> dict:
|
||||
"""Return the correct max tokens kwarg for the current provider.
|
||||
|
||||
OpenAI's newer models (gpt-4o, gpt-4.1, gpt-5+, o-series) require
|
||||
'max_completion_tokens'. Azure OpenAI and GitHub Copilot also require
|
||||
'max_completion_tokens' for those families served via their
|
||||
OpenAI-compatible endpoints. OpenRouter, local models, and older
|
||||
OpenAI's newer models (gpt-4o, o-series, gpt-5+) require
|
||||
'max_completion_tokens'. Azure OpenAI also requires
|
||||
'max_completion_tokens' for gpt-5.x models served via the
|
||||
OpenAI-compatible endpoint. OpenRouter, local models, and older
|
||||
OpenAI models use 'max_tokens'.
|
||||
|
||||
The check is URL-first (api.openai.com / Azure / Copilot all use the
|
||||
new kwarg), then falls back to a model-name check so third-party
|
||||
OpenAI-compatible endpoints fronting those models are recognised —
|
||||
URL-only detection misses that case and silently sends the wrong
|
||||
kwarg, which the upstream model rejects with a 400.
|
||||
"""
|
||||
if (
|
||||
self._is_direct_openai_url()
|
||||
or self._is_azure_openai_url()
|
||||
or self._is_github_copilot_url()
|
||||
or model_forces_max_completion_tokens(self.model)
|
||||
):
|
||||
if self._is_direct_openai_url() or self._is_azure_openai_url() or self._is_github_copilot_url():
|
||||
return {"max_completion_tokens": value}
|
||||
return {"max_tokens": value}
|
||||
|
||||
@@ -2831,18 +2816,11 @@ class AIAgent:
|
||||
if state is None:
|
||||
return
|
||||
try:
|
||||
from agent.credits_tracker import evaluate_credits_notices, is_free_tier_model
|
||||
from agent.credits_tracker import evaluate_credits_notices
|
||||
latch = getattr(self, "_credits_latch", None)
|
||||
if latch is None:
|
||||
latch = self._credits_latch = {"active": set(), "seen_below_90": False, "usage_band": None}
|
||||
# Free-model gate: a depleted account on a free model can still
|
||||
# inference, so the depleted error banner is suppressed. Local-data
|
||||
# only (":free" suffix + pricing-cache peek) — never a network call.
|
||||
model_is_free = is_free_tier_model(
|
||||
getattr(self, "model", "") or "",
|
||||
getattr(self, "base_url", "") or "",
|
||||
)
|
||||
to_show, to_clear = evaluate_credits_notices(state, latch, model_is_free=model_is_free)
|
||||
to_show, to_clear = evaluate_credits_notices(state, latch)
|
||||
for key in to_clear: # clears FIRST …
|
||||
self._emit_notice_clear(key)
|
||||
for notice in to_show: # … then shows (depleted lands last in a latest-wins slot)
|
||||
@@ -3109,17 +3087,6 @@ class AIAgent:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 6. Free conversation history. Mirrors _release_evicted_agent_soft's
|
||||
# soft-eviction clear — close() is the hard teardown for true session
|
||||
# boundaries (/new, /reset, session expiry), so the message list won't
|
||||
# be reused. Drops the reference proactively rather than waiting for
|
||||
# the agent object itself to be collected, which matters when a caller
|
||||
# still holds the closed agent (e.g. a draining background task).
|
||||
try:
|
||||
self._session_messages = []
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _hydrate_todo_store(self, history: List[Dict[str, Any]]) -> None:
|
||||
"""
|
||||
Recover todo state from conversation history.
|
||||
@@ -3937,13 +3904,6 @@ class AIAgent:
|
||||
def _anthropic_messages_create(self, api_kwargs: dict):
|
||||
if self.api_mode == "anthropic_messages":
|
||||
self._try_refresh_anthropic_client_credentials()
|
||||
# Defensive: strip Responses-only kwargs that can leak in under an
|
||||
# api_mode-flip race (the Anthropic SDK raises a non-retryable
|
||||
# TypeError on them). See #31673.
|
||||
from agent.anthropic_adapter import sanitize_anthropic_kwargs
|
||||
sanitize_anthropic_kwargs(
|
||||
api_kwargs, log_prefix=getattr(self, "log_prefix", "")
|
||||
)
|
||||
return self._anthropic_client.messages.create(**api_kwargs)
|
||||
|
||||
def _rebuild_anthropic_client(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user