#!/usr/bin/env python3 """Add Atlas per-turn routing controls to the community Hermes WebUI.""" import os from pathlib import Path ROOT = Path(os.environ.get("HERMES_WEBUI_PATCH_ROOT", "/opt/hermes-webui")) def replace_once(path: Path, before: str, after: str, label: str) -> None: """Replace one pinned upstream fragment or fail the image build.""" source = path.read_text(encoding="utf-8") count = source.count(before) if count != 1: raise SystemExit(f"{label} context changed: expected 1, found {count}") path.write_text(source.replace(before, after, 1), encoding="utf-8") index = ROOT / "static/index.html" replace_once( index, '''
\n''', '''
\n''', "routing chip", ) replace_once( index, '''
\n''', '''
\n''', "routing dropdown", ) replace_once( index, '''\n''', ''' \n''', "router script", ) style = ROOT / "static/style.css" replace_once( style, ''' .composer-reasoning-chip{display:inline-flex;align-items:center;gap:5px;max-width:none;padding:8px 10px;border-radius:999px;border:1px solid transparent;background-color:transparent;color:var(--muted);font-weight:500;cursor:pointer;transition:color .15s,background-color .15s,border-color .15s;}\n''', ''' .composer-routing-chip{display:inline-flex;align-items:center;gap:5px;max-width:none;padding:8px 10px;border-radius:999px;border:1px solid var(--accent-bg);background:var(--accent-bg);color:var(--accent-text);font-weight:600;cursor:pointer;transition:color .15s,background-color .15s,border-color .15s;} .composer-routing-chip:hover,.composer-routing-chip.active{color:var(--text);border-color:var(--accent);} .composer-routing-label{font-size:11px;font-weight:700;letter-spacing:.04em;} .composer-routing-chevron{font-size:11px;line-height:1;} .composer-routing-dropdown{display:none;position:absolute;bottom:calc(100% + 4px);left:0;min-width:250px;background:var(--surface);border:1px solid var(--border2);border-radius:10px;box-shadow:0 -4px 24px rgba(0,0,0,.4);z-index:205;padding:4px;overflow:hidden;} .composer-routing-dropdown.open{display:block;} .routing-option{display:flex;flex-direction:column;gap:2px;padding:8px 14px;border-radius:6px;cursor:pointer;font-size:13px;color:var(--text);white-space:nowrap;transition:background-color .12s;} .routing-option span{font-size:11px;color:var(--muted);font-weight:400;} .routing-option:hover{background:rgba(255,255,255,.07);} .routing-option.selected{background:var(--accent-bg);} .composer-reasoning-chip{display:inline-flex;align-items:center;gap:5px;max-width:none;padding:8px 10px;border-radius:999px;border:1px solid transparent;background-color:transparent;color:var(--muted);font-weight:500;cursor:pointer;transition:color .15s,background-color .15s,border-color .15s;}\n''', "routing styles", ) messages = ROOT / "static/messages.js" replace_once( messages, ''' explicit_model_pick:_explicitPick||undefined, attachments:uploaded.length?uploaded:undefined, ''', ''' explicit_model_pick:_explicitPick||undefined, ...((typeof window.hermesRoutingRequest==='function')?window.hermesRoutingRequest():{}), attachments:uploaded.length?uploaded:undefined, ''', "chat routing payload", ) ui = ROOT / "static/ui.js" replace_once( ui, ''' _currentReasoningEffort=effort; ''', ''' _currentReasoningEffort=effort; window._atlasCurrentReasoningEffort=effort; ''', "reasoning effort bridge", ) routes = ROOT / "api/routes.py" replace_once( routes, ''' gateway_chat_enabled: bool | None = None, ): ''', ''' gateway_chat_enabled: bool | None = None, routing_priority: str = "", explicit_model_pick: bool = False, explicit_reasoning_effort: str = "", ): ''', "start run routing arguments", ) replace_once( routes, ''' external_runtime_owned=gateway_chat_enabled, ) ''', ''' external_runtime_owned=gateway_chat_enabled, routing_priority=routing_priority, explicit_model_pick=explicit_model_pick, explicit_reasoning_effort=explicit_reasoning_effort, ) ''', "adapter routing forwarding", ) replace_once( routes, ''' external_runtime_owned=gateway_chat_enabled, ) def _process_wakeup_revalidation_provider''', ''' external_runtime_owned=gateway_chat_enabled, routing_priority=routing_priority, explicit_model_pick=explicit_model_pick, explicit_reasoning_effort=explicit_reasoning_effort, ) def _process_wakeup_revalidation_provider''', "direct routing forwarding", ) replace_once( routes, ''' external_runtime_owned: bool | None = None, ): ''', ''' external_runtime_owned: bool | None = None, routing_priority: str = "", explicit_model_pick: bool = False, explicit_reasoning_effort: str = "", ): ''', "stream routing arguments", ) replace_once( routes, ''' worker_kwargs = {"model_provider": model_provider, "goal_related": goal_related} ''', ''' worker_kwargs = {"model_provider": model_provider, "goal_related": goal_related} if backend_is_gateway: worker_kwargs.update({ "routing_priority": routing_priority, "explicit_model_pick": explicit_model_pick, "explicit_reasoning_effort": explicit_reasoning_effort, }) ''', "gateway worker routing arguments", ) replace_once( routes, ''' explicit_model_pick = bool(body.get("explicit_model_pick")) moa_config = None ''', ''' explicit_model_pick = bool(body.get("explicit_model_pick")) routing_priority = str(body.get("routing_priority") or "").strip().lower() if routing_priority not in {"", "auto", "fast", "balanced", "deep", "maximum"}: return bad(handler, "invalid routing priority", 400) if routing_priority == "auto": routing_priority = "" explicit_reasoning_effort = str( body.get("explicit_reasoning_effort") or "" ).strip().lower() if explicit_reasoning_effort not in { "", "none", "minimal", "low", "medium", "high", "xhigh" }: return bad(handler, "invalid explicit reasoning effort", 400) moa_config = None ''', "chat routing validation", ) replace_once( routes, ''' "gateway_chat_enabled": gateway_chat_enabled, } ''', ''' "gateway_chat_enabled": gateway_chat_enabled, "routing_priority": routing_priority, "explicit_model_pick": explicit_model_pick, "explicit_reasoning_effort": explicit_reasoning_effort, } ''', "chat routing start arguments", ) gateway = ROOT / "api/gateway_chat.py" replace_once( gateway, ''' model_provider=None, goal_related=False, ): ''', ''' model_provider=None, goal_related=False, routing_priority="", explicit_model_pick=False, explicit_reasoning_effort="", ): ''', "gateway routing arguments", ) replace_once( gateway, ''' if _gw_overrides.get("service_tier"): body_extras["service_tier"] = _gw_overrides["service_tier"] ''', ''' if _gw_overrides.get("service_tier"): body_extras["service_tier"] = _gw_overrides["service_tier"] body_extras["routing_priority"] = routing_priority body_extras["explicit_model_pick"] = bool(explicit_model_pick) body_extras["explicit_reasoning_effort"] = explicit_reasoning_effort ''', "runs API routing body", )