From 8bec1540f0c3d35585715452c2f685e54ac515cf Mon Sep 17 00:00:00 2001 From: antydizajn Date: Mon, 1 Jun 2026 23:14:38 +0200 Subject: [PATCH] tui: centralize RID-strip in format_model_for_display + apply to switch banner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review on PR #36998: the inline ri.... 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. --- cli.py | 30 +++++++++++++------------ gateway/slash_commands.py | 23 ++++++++++++++----- hermes_cli/model_switch.py | 45 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 20 deletions(-) diff --git a/cli.py b/cli.py index 044f265e297..4d370aa221f 100644 --- a/cli.py +++ b/cli.py @@ -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, diff --git a/gateway/slash_commands.py b/gateway/slash_commands.py index 03c3017c8ba..419a11ad293 100644 --- a/gateway/slash_commands.py +++ b/gateway/slash_commands.py @@ -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, diff --git a/hermes_cli/model_switch.py b/hermes_cli/model_switch.py index ee58f99ef87..5d58c5911eb 100644 --- a/hermes_cli/model_switch.py +++ b/hermes_cli/model_switch.py @@ -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.