tui: centralize RID-strip in format_model_for_display + apply to switch banner

Address review on PR #36998: the inline ri.<service>..<ns>. stripper in
_get_status_bar_snapshot was a one-off heuristic that:

  * lived in cli.py with no shared call site, so the switch-confirmation
    banner ("✓ Model switched: ri.language-model-service..…") and the
    [Note: model was just switched from … to …] system-prompt nudge still
    printed the full opaque RID — exactly what the screenshot reported;
  * split on '..' and re-split on '.', which would mis-handle any RID
    whose namespace token isn't a single dotted segment.

Refactor:

  * New module-level helper hermes_cli.model_switch.format_model_for_display
    matches on a startswith() allow-list (_OPAQUE_MODEL_PREFIXES) and
    returns the trailing slug. Falls through to the original string for
    every non-Palantir id, so HF paths (meta-llama/Llama-3.3-70B-Instruct),
    plain Claude/GPT names, .gguf paths, and aliased ids are untouched.
    Allow-list is extensible — add a prefix tuple entry for future
    proxies that wrap real names in a namespace (Bedrock ARNs are
    already covered by the slash-split fallback and have a different shape).

  * _get_status_bar_snapshot() now delegates to the shared helper after
    the reverse-alias miss (so configured aliases still win over the
    helper output).

  * cli.py::_handle_model_command — both confirmation-print blocks
    (~7720 and ~7975) now run result.new_model AND old_model through
    the formatter before they hit _cprint() and the
    _pending_model_switch_note text.

  * gateway/run.py model-switch handler (~10915) — same treatment for
    _pending_model_notes[_session_key] and the
    t('gateway.model.switched', model=…) confirmation line returned to
    the gateway client.

The formatter is DISPLAY-ONLY. The session_model_overrides map,
ModelSwitchResult.new_model, persistence to config.yaml, alias lookups,
and every wire call still carry the full opaque RID — Palantir's API
requires it.

Verification: unit reproducer covers (a) all four Palantir model RIDs
from this user's config stripped to the trailing slug, (b) plain
model names (claude-4-7-opus-20260101, gpt-5.4, HF paths, empty
string) passed through unchanged, (c) prefix-only edge preserved
(no infinite-loop / empty-output regression).

Refs: PR #36998 review feedback; screenshot showed model banner still
printing the long RID after the original status-bar-only fix landed.
This commit is contained in:
antydizajn 2026-06-01 23:14:38 +02:00 committed by Teknium
parent 4e02320ed9
commit 8bec1540f0
3 changed files with 78 additions and 20 deletions

30
cli.py
View file

@ -4633,16 +4633,10 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
model_short = _reverse_alias_for_display(model_name)
if model_short == model_name:
model_short = model_name.split("/")[-1] if "/" in model_name else model_name
# Strip Palantir RID prefixes that survived the slash split:
# ``ri.language-model-service..language-model.anthropic-claude-4-7-opus``
# → ``claude-4-7-opus``. The double-dot is Palantir's RID separator.
if model_short.startswith("ri.") and ".." in model_short:
_tail = model_short.split("..", 1)[1]
# Drop the leading namespace token (``language-model.``).
if "." in _tail:
_tail = _tail.split(".", 1)[1]
if _tail:
model_short = _tail
# Strip Palantir RID prefixes via the shared display formatter so
# this site and ``ModelSwitchResult`` confirmation can't drift.
from hermes_cli.model_switch import format_model_for_display
model_short = format_model_for_display(model_short)
if model_short.endswith(".gguf"):
model_short = model_short[:-5]
if len(model_short) > 26:
@ -8058,14 +8052,18 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
)
return
from hermes_cli.model_switch import format_model_for_display
_display_old = format_model_for_display(old_model)
_display_new = format_model_for_display(result.new_model)
self._pending_model_switch_note = (
f"[Note: model was just switched from {old_model} to {result.new_model} "
f"[Note: model was just switched from {_display_old} to {_display_new} "
f"via {result.provider_label or result.target_provider}. "
f"Adjust your self-identification accordingly.]"
)
provider_label = result.provider_label or result.target_provider
_cprint(f" ✓ Model switched: {result.new_model}")
_cprint(f" ✓ Model switched: {_display_new}")
_cprint(f" Provider: {provider_label}")
# Context: always resolve via the provider-aware chain so Codex OAuth,
@ -8389,8 +8387,12 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
# Store a note to prepend to the next user message so the model
# knows a switch occurred (avoids injecting system messages mid-history
# which breaks providers and prompt caching).
from hermes_cli.model_switch import format_model_for_display
_display_old = format_model_for_display(old_model)
_display_new = format_model_for_display(result.new_model)
self._pending_model_switch_note = (
f"[Note: model was just switched from {old_model} to {result.new_model} "
f"[Note: model was just switched from {_display_old} to {_display_new} "
f"via {result.provider_label or result.target_provider}. "
f"{'This override applies to the next turn only. ' if one_turn else ''}"
f"Adjust your self-identification accordingly.]"
@ -8402,7 +8404,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
# Display confirmation with full metadata
provider_label = result.provider_label or result.target_provider
_cprint(f" ✓ Model switched: {result.new_model}")
_cprint(f" ✓ Model switched: {_display_new}")
_cprint(f" Provider: {provider_label}")
# Context: always resolve via the provider-aware chain so Codex OAuth,

View file

@ -1663,11 +1663,17 @@ class GatewaySlashCommandsMixin:
"Failed to persist model switch to DB: %s", exc
)
# Store model note + session override
# Store model note + session override. Use display
# form (strips opaque Palantir prefix) for the user-
# visible note; session-override map still gets the
# full opaque ID, which is what the wire needs.
from hermes_cli.model_switch import format_model_for_display
_display_cur = format_model_for_display(_cur_model)
_display_new = format_model_for_display(result.new_model)
if not hasattr(_self, "_pending_model_notes"):
_self._pending_model_notes = {}
_self._pending_model_notes[_session_key] = (
f"[Note: model was just switched from {_cur_model} to {result.new_model} "
f"[Note: model was just switched from {_display_cur} to {_display_new} "
f"via {result.provider_label or result.target_provider}. "
f"Adjust your self-identification accordingly.]"
)
@ -1743,9 +1749,11 @@ class GatewaySlashCommandsMixin:
except Exception as e:
logger.warning("Failed to persist model switch: %s", e)
# Build confirmation text
# Build confirmation text. Use display form so opaque
# Palantir IDs (ri.language-model-service..*) get
# shortened to their trailing slug for the UI.
plabel = result.provider_label or result.target_provider
lines = [t("gateway.model.switched", model=result.new_model)]
lines = [t("gateway.model.switched", model=format_model_for_display(result.new_model))]
lines.append(t("gateway.model.provider_label", provider=plabel))
mi = result.model_info
from hermes_cli.model_switch import resolve_display_context_length
@ -1939,10 +1947,13 @@ class GatewaySlashCommandsMixin:
# Store a note to prepend to the next user message so the model
# knows about the switch (avoids system messages mid-history).
# Display form strips opaque Palantir RID prefixes; the override
# map below keeps the full ID for the wire.
from hermes_cli.model_switch import format_model_for_display
if not hasattr(self, "_pending_model_notes"):
self._pending_model_notes = {}
self._pending_model_notes[session_key] = (
f"[Note: model was just switched from {current_model} to {result.new_model} "
f"[Note: model was just switched from {format_model_for_display(current_model)} to {format_model_for_display(result.new_model)} "
f"via {result.provider_label or result.target_provider}. "
f"{'This override applies to the next turn only. ' if one_turn else ''}"
f"Adjust your self-identification accordingly.]"
@ -2038,7 +2049,7 @@ class GatewaySlashCommandsMixin:
# Build confirmation message with full metadata
provider_label = result.provider_label or result.target_provider
lines = [t("gateway.model.switched", model=result.new_model)]
lines = [t("gateway.model.switched", model=format_model_for_display(result.new_model))]
lines.append(t("gateway.model.provider_label", provider=provider_label))
# Context: always resolve via the provider-aware chain so Codex OAuth,

View file

@ -200,6 +200,51 @@ _NOUS_HERMES_NON_AGENTIC_RE = re.compile(
)
# Opaque internal model-ID display
# ---------------------------------------------------------------------------
# Some proxies (notably Palantir Foundry's LLM-proxy) identify models by
# resource-instance IDs that are deeply nested, verbose, and pure noise to
# read in CLI status output, e.g.:
#
# ri.language-model-service..language-model.anthropic-claude-4-7-opus
#
# The provider_label (e.g. "palantir-claude46") already carries the routing
# context, so the only useful information left in the opaque ID is the
# trailing slug. Strip the boilerplate prefix for *display* — never for
# wire-side comparison, persistence, config writes, alias lookup, or
# anything that round-trips back into the API.
#
# Match by substring on a known prefix so we never accidentally truncate
# a legitimate model name that happens to contain dots.
_OPAQUE_MODEL_PREFIXES: tuple[str, ...] = (
"ri.language-model-service..language-model.",
)
def format_model_for_display(model_name: str) -> str:
"""Return a human-friendly form of *model_name* for CLI status output.
Strips known opaque proxy prefixes (Palantir Foundry's
``ri.language-model-service..language-model.*``) and returns the
trailing slug. Falls through to the original string for everything
else, so real model IDs (``claude-4-7-opus-20260101``,
``gpt-5-4``, ``meta-llama/Llama-3.3-70B-Instruct``) are untouched.
This is a DISPLAY-ONLY helper. Do NOT use the return value for any
wire-side operation the proxy expects the full opaque ID, and
callers that compare or persist must keep the original.
"""
if not model_name:
return model_name
for prefix in _OPAQUE_MODEL_PREFIXES:
if model_name.startswith(prefix):
tail = model_name[len(prefix):]
return tail if tail else model_name
return model_name
# ---------------------------------------------------------------------------
def is_nous_hermes_non_agentic(model_name: str) -> bool:
"""Return True if *model_name* is a real Nous Hermes 3/4 chat model.