fix(model): require confirmation for expensive model selections

Rebased onto current main and re-ported across the restructured
surfaces: model flows now thread confirm_provider/base_url/api_key
through hermes_cli/model_setup_flows.py, the Discord picker lives in
plugins/platforms/discord/adapter.py, and the web dashboard picker
applies chat-mode switches via config.set so the expensive-model
confirmation can ride the response.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Robin Fernandes
2026-06-10 00:24:06 -07:00
committed by Teknium
co-authored by Claude Fable 5
parent 4eadef18a9
commit af978ecb17
27 changed files with 1354 additions and 111 deletions
+74 -2
View File
@@ -3030,7 +3030,7 @@ class TelegramAdapter(BasePlatformAdapter):
async def _handle_model_picker_callback(
self, query, data: str, chat_id: str
) -> None:
"""Handle model picker inline keyboard callbacks (mp:/mm:/mb:/mx:/mg:)."""
"""Handle model picker inline keyboard callbacks (mp:/mm:/mc:/mb:/mx:/mg:)."""
state = self._model_picker_state.get(chat_id)
if not state:
await query.answer(text="Picker expired — use /model again.")
@@ -3115,6 +3115,51 @@ class TelegramAdapter(BasePlatformAdapter):
)
await query.answer()
elif data.startswith("mc:"):
# --- Expensive model confirmed: perform the switch ---
try:
idx = int(data[3:])
except ValueError:
await query.answer(text="Invalid selection.")
return
model_list = state.get("model_list", [])
if idx < 0 or idx >= len(model_list):
await query.answer(text="Invalid model index.")
return
model_id = model_list[idx]
provider_slug = state.get("selected_provider", "")
callback = state.get("on_model_selected")
if not callback:
await query.answer(text="Picker expired.")
return
try:
result_text = await callback(chat_id, model_id, provider_slug)
except Exception as exc:
logger.error("Model picker switch failed: %s", exc)
result_text = f"Error switching model: {exc}"
try:
await query.edit_message_text(
text=self.format_message(result_text),
parse_mode=ParseMode.MARKDOWN_V2,
reply_markup=None,
)
except Exception:
try:
await query.edit_message_text(
text=result_text,
parse_mode=None,
reply_markup=None,
)
except Exception:
pass
await query.answer(text="Model switched!")
self._model_picker_state.pop(chat_id, None)
elif data.startswith("mm:"):
# --- Model selected: perform the switch ---
try:
@@ -3136,6 +3181,33 @@ class TelegramAdapter(BasePlatformAdapter):
await query.answer(text="Picker expired.")
return
try:
from hermes_cli.model_cost_guard import expensive_model_warning
warning = expensive_model_warning(
model_id,
provider=provider_slug,
)
except Exception:
warning = None
if warning is not None:
keyboard = InlineKeyboardMarkup([
[InlineKeyboardButton("Switch anyway", callback_data=f"mc:{idx}")],
[
InlineKeyboardButton("◀ Back", callback_data="mb"),
InlineKeyboardButton("✗ Cancel", callback_data="mx"),
],
])
await query.edit_message_text(
text=self.format_message(
f"⚠ *Expensive Model Warning*\n\n{warning.message}"
),
parse_mode=ParseMode.MARKDOWN_V2,
reply_markup=keyboard,
)
await query.answer(text="Confirm expensive model")
return
try:
result_text = await callback(chat_id, model_id, provider_slug)
except Exception as exc:
@@ -3260,7 +3332,7 @@ class TelegramAdapter(BasePlatformAdapter):
query_user_name = getattr(query.from_user, "first_name", None)
# --- Model picker callbacks ---
if data.startswith(("mp:", "mpg:", "mm:", "mb", "mx", "mg:")):
if data.startswith(("mp:", "mpg:", "mm:", "mc:", "mb", "mx", "mg:")):
chat_id = str(query.message.chat_id) if query.message else None
if chat_id:
await self._handle_model_picker_callback(query, data, chat_id)