
    *jǅ                    @!   U d Z ddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddlZddlmZ ddlmZmZ  ej                    Z ee          j        j                                        ZdefdZ ej        dd          Z e ej        d	d
                    Z ej        dd                                          pdZ ej        dd                                          pdZ eduoe duZ! e            Z" e ej        d e#e"dz                                $                                                                Z%e%dz  Z&e%dz  Z'e&dz  Z(e%dz  Z)e%dz  Z*e%dz  Z+ ej,        e-          Z.de#dedefdZ/defdZ0dede#fdZ1 e0            Z2 e1e2          Z3e28 e#e2          e	j4        vr#e	j4        5                     e#e2                     dZ6ndZ6i Z7 e
j8                    Z9da:e;e<d<   da=edz  e<d <   da>e#dz  e<d!<   d"e?de#fd#Z@deAfd$ZBdefd%ZCd&d'hZDd&ZEde?fd(ZFdd)e?dz  de#fd*ZGdd+ZHd,ede?fd-ZId,ed)e?ddfd.ZJ eH             e7ZKdd/e#ez  dz  deLe         fd0ZMd1edeAfd2ZNdd/e#ez  dz  defd3ZOdefd4ZP eP            aQ ej        d5d          ZRdd6ZSdeTfd7ZUd8ZV e/d9d:          ZWh d;ZXh d<ZYh d=ZZi d>d?d@dAdBdAdCdDdEdFdGdHdIdJdKdLdMdNdOdPdQdRdSdRdTdUdVdWdXdYdZd[d\d]d^d_d`dadadbdcdddedddfdgdhZ[g diZ\djdkiZ]dl Z^ddmZ_ e_            Z`g dndodpdqdndrdsdqdtdudvdqdtdwdxdqdtdydzdqdtd{d|dqdtd}d~dqddddqddddqddddqddddqddddqddddqddddqddddqddddqddddqddddqddddqddddqddddqddddqddddqddddqddddqddddqddddqddddqddddqddddqddddqddddqddddqZai ddddddtddnddddÓddœddǓddɓdd˓ddddddϓddddғddԓdd֓ddddddddddddߜZbi ddȓddȓddȓddȓddēddēddēddēddddddddʓddʓdddddd̓ddΓi ddΓddddddddddddddddddՓddՓddՓd dՓddddddddddddZcde#de#fd	Zddeede#fd
Zfdde?dz  deLe?         fdZgdde?dz  dehe#         fdZi	 ddeede?dz  de#fdZj	 dddddeede?dz  deedeAde#f
dZkdeede#fdZldeede#fdZmdeedehe#         fdZn eh            Zoehe#         e<d<   deede#fdZpdeede#fdZqdeede#dz  fdZr	 ddeede?dz  de#fdZsi dddvdd dxdd!dzdd"d|dd#d~dgdd$d%dd&d'dd(dpdd)dsdgdd$d%dd&d'dd)dsdd(dpdd*d+dd,d-dd.d/dd0d1dd2d3dg	dАd4ddd5ddd6ddd7ddd8ddgd̐d9ddd:ddd;ddd<d=dgdd>d?dd@dAddBdCddDdEdgdȐdFdddGdddHdddIdddJdddKddgdʐdLdMddNdOddPdQddRdSddTdUdgd͐dVdddWdddXdYddZd[dd\d]dgdΐdVdddXdYdd\d]dd^d_dgdĐd$d%dd&d'dd)dsdd(dpdd`dadd dxdd!dzdd5ddgdƐdbdcddddeddfdgdddǐdgdg dhdidd)dsdd(dpddjdkdd*d+ddldmddndodd,d-ddpdqddrdsdd.d/dd0d1ddtduddvdwddxdyddzdvdd{dxdd|d}dd~ddddzdd"d|ddddd#d~ddddd4ddd5ddd6ddd7ddd8dddFdddGdddTdUdddYddddddddddddFdddGdddTdUddddd:ddd9dddddddddddddddddddYdddddddgdd4ddd5ddd6ddd7ddd8ddgdddddddgdddddddgddddddddddddgdddddddddddddddgdddgdddgddddddddddddddddddgdZt euddh          Zvde#de#de#deAfdZwde#de#fdZxde#de#fdZydZzdZ{dZ|de{dĜdeLe#         de#dz  dedeTeLe#         eLe#         f         fdȄZ}deLe?         de#de#dz  deLe?         fd˄Z~deLe?         ddfd̈́Zh dΣZde#deAfdτZde#deAfdЄZde#deAfd҄Zdӄ Zde#deTfdՄZde#deTe#dz  e#dz  f         fdքZ eudƐdh          Zdde#de#dz  de#fdلZdd)e?dz  de#fdڄZdZd܄ Zde#de#fd݄Zde#de#deLe#         fdބZ	 	 	 dde#dz  de#dz  de#dz  deLe#         fd߄Zddddde#dz  de#dz  de#dz  de?fdZdeAde?fdZde#de?fdZde#de?fdZdZeTe#df         e<d<   de?fdZde#de#de#de?fdZdae?dz  e<d<   dae;e<d<   dae?dz  e<d<   dZe;e<d<    e
j                    Z e
j        e          adai ae?e#eTe;df         f         e<d<   i Ze?e#e;f         e<d<   de#dz  fdZdZe%dz  ZdefdZd1ede?fdZde?fdZ euh d          Zd Zd1ede?fdZde?fdZdd ZdeedeAfdZdeedeAfdZde?dz  fdZde?ddfdZde;de?dz  fdZd Zde#fd	Zde#fd
Zde#deLde#fdZde#deLe#         fdZde#deLe#         deLe?         fdZdeLe#         fdZde?fdZedz  dz  Z e
j8                    ZdZ e
j8                    Z G d d          ZdefdZi Ze?e<d<    e
j8                    Zi Ze?e<d<   i Ze?e<d<   i Ze?e<d<   i Ze?e<d<   i Ze?e<d<   i Ze?e<d<   i Ze?e<d<    eh            Zehe<d<   i Ze?e<d <    e
j8                    Zdae;dz  e<d!<    ej                    Z̐d"e#ddfd#Z͐d"e#ddfd$Zΐd"e#ddfd%ZddlZ ej                    Zej        e<d&<   d'Z e
j8                    ZӐd(e#ddfd)Z e
j                    Z֐d* Zאd+ Zi Ze?e<d,<    e
j8                    Zڐd(e#de
j8        fd-Zi d. e#t                    d/dd0d1d2dd3dd4dd5dd6dd7dd8dd9dd:dd;dd<dd=d>d?d@dAd@i dBddCddDddEdFdGdHdIdJdKdLdMdNdOg dPdQdR ej        dSdT          dUddVddWddXddYddZdd[d\d]dd^Zh d_Zh d`Zh daZߐdbdcdddbdedfZdeTe#e#f         fdgZde?fdhZ eheܠ                                          didjhz
  Zd1dkhd[dlhh dmh dnh dodpZdqdrdsdtdudvdwZh dxZ edy                              dz          Zd{e?de?fd|Z e            Z	 e)                                Zn# e$ r dZY nw xY wer ej        d}          s eOe                    d.                    aQe                    djd           e                    d.           e#t                    k    rM e#t                    ed.<   	 e)                     ej        edd~          d           n# e$ r Y nw xY w ej                    Zej        e<d<   	 ddlmZ  e             dS # e$ r Y dS w xY w(  a  
Hermes Web UI -- Shared configuration, constants, and global state.
Imported by all other api/* modules and by server.py.

Discovery order for all paths:
  1. Explicit environment variable
  2. Filesystem heuristics (sibling checkout, parent dir, common install locations)
  3. Hardened defaults relative to $HOME
  4. Fail loudly with a human-readable fix-it message if required modules are missing
    N)Path)parse_qsurlparsereturnc                      t           j        dk    r;t          j        dd                                          } | rt	          |           dz  S t
          dz  S )zReturn the platform-aware default Hermes home when HERMES_HOME is unset.

    Native Windows Hermes Agent installs default to %LOCALAPPDATA%\hermes,
    while POSIX installs use ~/.hermes.
    ntLOCALAPPDATA hermesz.hermes)osnamegetenvstripr   HOME)local_app_datas    -/Users/bertmahoney/hermes-webui/api/config.py_platform_default_hermes_homer   "   sR     
w$>266<<>> 	3''(22)    HERMES_WEBUI_HOST	127.0.0.1HERMES_WEBUI_PORT8787HERMES_WEBUI_TLS_CERTr
   HERMES_WEBUI_TLS_KEYHERMES_WEBUI_STATE_DIRwebuisessionszworkspaces.jsonz_index.jsonzsettings.jsonzlast_workspace.txtzprojects.jsonr   
default_mbc                    t          j        | d                                          }|s|dz  dz  S t          j        d|t          j                  }|s%t                              d| ||           |dz  dz  S t          |	                    d                    }|dk    r%t                              d| ||           |dz  dz  S |dz  dz  S )zParse an optional megabyte environment variable into bytes.

    Accepts values like ``200``, ``200MB``, or ``200MiB``. Invalid or
    non-positive values fall back to the provided default.
    r
   i   z^(\d+)\s*(?:m|mb|mib)?$zGInvalid %s=%r; expected a positive integer in MB. Falling back to %sMB.   r   zHInvalid %s=%r; expected a value greater than zero. Falling back to %sMB.)
r   r   r   rematch
IGNORECASEloggerwarningintgroup)r   r   rawmvalue_mbs        r   _env_mb_bytesr+   J   s     )D"


#
#
%
%C (D 4''
+S"-@@A (U		
 	
 	
 D 4''1771::H1}}V		
 	
 	
 D 4''d?T!!r   c            	      ~   g } t          j        d          rX|                     t          t          j        d                                                                                               t          j        dt          t                              }|                     t          |                                          dz             |                     t          j	        dz             t          j	        dz  
                                r|                     t          j	                   |                     t          dz             |                     t          dz             t          t          j        dt          t          dz  dz                                }|                     |                                dz             dD ]'}|                     t          |          dz             (| D ]C}|
                                r-|dz  
                                r|                                c S Dd	S )
a  
    Locate the hermes-agent checkout using a multi-strategy search.

    Priority:
      1. HERMES_WEBUI_AGENT_DIR env var  -- explicit override always wins
      2. HERMES_HOME / hermes-agent      -- e.g. ~/.hermes/hermes-agent
      3. Sibling of this repo            -- ../hermes-agent
      4. Parent of this repo             -- ../../hermes-agent (nested layout)
      5. Common install paths            -- ~/.hermes/hermes-agent (again as fallback)
      6. HOME / hermes-agent             -- ~/hermes-agent (simple flat layout)
    HERMES_WEBUI_AGENT_DIRHERMES_HOMEzhermes-agentzrun_agent.pyXDG_DATA_HOMEz.localshare)z/optz
/usr/localz/usr/local/shareN)r   r   appendr   
expanduserresolvestr_DEFAULT_HERMES_HOME	REPO_ROOTparentexistsr   )
candidateshermes_homexdg_data
sys_prefixpaths        r   _discover_agent_dirr>   i   s    J 
y)** 
34455@@BBJJLL	
 	
 	

 )M3/C+D+DEEKd;''2244~EFFF i&7888 	>)1133 ,)*+++ *^;<<< d^+,,, BIos4(?W3L/M/MNNOOHh))++n<=== A = =
$z**^;<<<< " ";;== 	"d^3;;== 	"<<>>!!!4r   	agent_dirc                 x   t          j        d          rt          j        d          S | r| dz  dz  dz  }|                                rt          |          S | dz  dz  dz  }|                                rt          |          S | dz  dz  dz  }|                                rt          |          S | dz  dz  dz  }|                                rt          |          S t          dz  dz  dz  }|                                rt          |          S dd	l}d
D ]}|                    |          }|r|c S dS )z
    Locate a Python executable that has the Hermes agent dependencies installed.

    Priority:
      1. HERMES_WEBUI_PYTHON env var
      2. Agent venv at <agent_dir>/venv/bin/python
      3. Local .venv inside this repo
      4. System python3
    HERMES_WEBUI_PYTHONvenvbinpythonz.venvScriptsz
python.exer   N)python3rD   rF   )r   r   r8   r4   r6   shutilwhich)r?   venv_pyvenv_py_win
local_venvrG   r   founds          r   _discover_pythonrM      s    
y&'' 0y./// $f$u,x7>> 	 w<<g%-8>> 	 w<<  &(94|C 	${###')I5D 	${### W$u,x7J : MMM%  T"" 	LLL	 9r   TF        
_cfg_mtime	_cfg_path_cfg_fingerprintdatac                 |    	 t          j        | ddt                    S # t          $ r t	          |           cY S w xY w)az  Return a stable fingerprint for config dictionaries.

    A few tests and legacy call sites still mutate ``cfg`` directly for
    in-memory overrides.  Path-aware reloads should not immediately discard
    those overrides just because the active profile path differs from the last
    disk load, but an unchanged disk-loaded cache must still reload on profile
    switches.
    T,:)	sort_keys
separatorsdefault)jsondumpsr4   	Exceptionrepr)rR   s    r   _fingerprint_configr^      sM    z$$:sSSSS   Dzzs    ;;c                      t           t          t                    t           k    rdS 	 t          t          uS # t          $ r Y dS w xY w)uF  True when cfg was changed after the last successful reload_config().

    Detects two override shapes:
      1. ``_cfg_cache`` was mutated in place (fingerprint differs).
      2. ``cfg`` (the module attribute) was rebound to a different dict —
         e.g. ``monkeypatch.setattr(config, "cfg", {...})`` in tests. The
         alias-with-the-cache pattern at module load means this is a common
         test-isolation override, and silently reloading from disk over it
         (the v0.51.7 path-aware reload regression) breaks any test that
         relies on the override.
    NTF)rQ   r^   
_cfg_cachecfg	NameError r   r   _cfg_has_in_memory_overridesrd      sU     #(;J(G(GK[([([t*$$   uus   6 
AAc                      t          j        d          } | r!t          |                                           S 	 ddlm}  |            dz  S # t          $ r t          dz  cY S w xY w)z/Return config.yaml path for the active profile.HERMES_CONFIG_PATHr   get_active_hermes_homezconfig.yaml)r   r   r   r2   api.profilesrh   ImportErrorr5   )env_overriderh   s     r   _get_config_pathrl     s    9122L /L!!,,...4777777%%''-77 4 4 4#m33334s   A A#"A#deferredeagerc                  J   t                      } 	 |                                 j        }n# t          $ r d}Y nw xY w|t          k    p
t
          | k    }t          r|rt                      st                       	 t          t          urt          S n# t          $ r Y nw xY wt          S )z;Return the cached config dict, loading from disk if needed.rN   )rl   statst_mtimeOSErrorrO   rP   r`   rd   reload_configra   rb   )config_pathcurrent_mtimecache_stales      r   
get_configrw   $  s    "$$K#((**3   :-Ik1IK + .J.L.L j  J !   s   * 998B 
BBconfig_datac                    t          | t                    r| nt          }t          |t                    r|                    di           ni }t          |t                    st          S |                    dt                    }t          |t
                    r1|                                                                }|t          v r|S t          S )a  Return the validated first-turn session persistence mode.

    ``deferred`` preserves the current first-turn sidecar behaviour: persist
    pending_user_message/runtime fields before streaming, then merge the turn
    after the agent finishes. ``eager`` additionally checkpoints the current
    user turn into ``messages`` before launching the agent thread. Unknown
    values fail closed to ``deferred`` so a typo never reintroduces eager disk
    writes unexpectedly.
    r   session_save_mode)	
isinstancedictra   get _DEFAULT_WEBUI_SESSION_SAVE_MODEr4   r   lower_WEBUI_SESSION_SAVE_MODES)rx   
active_cfg	webui_cfgmode
normalizeds        r   get_webui_session_save_moder   ;  s     !+; = =F3J/9*d/K/KS
w+++QSIi&& 0//==,.NOOD$ ZZ\\''))
222++r   c                     t           5  t                                           t                      } t          }| ada	 ddl}|                                 r|                    | 	                    d                    }t          |t                    rTt                              |           	 t          |                                           j        an# t           $ r daY nw xY wn+# t"          $ r t$                              d|            Y nw xY wt)          t                    a|dk    rt-                       ddd           dS # 1 swxY w Y   dS )z7Reload config.yaml from the active profile's directory.rN   r   Nutf-8encodingz"Failed to load yaml config from %s)	_cfg_lockr`   clearrl   rO   rP   yamlr8   	safe_load	read_textr{   r|   updater   rp   rq   rr   r\   r$   debugr^   rQ   _delete_models_cache_on_disk)rt   _old_cfg_mtime_yamlloadeds       r   rs   rs   Q  s    
 + +&(( $	
	L    !!## ))>)>)>)P)PQQfd++ )%%f---)%)+%6%6%;%;%=%=%F

" ) ) )%(


) 	L 	L 	LLL={KKKKK	L.z:: S  (***9+ + + + + + + + + + + + + + + + + +sY   3EA0C'-&CC'C# C'"C##C'&E'%DED+EEErt   c                 >   	 dd l }n# t          $ r i cY S w xY w|                                 si S 	 |                    |                     d                    }t          |t                    r|ni S # t          $ r  t          	                    d|            i cY S w xY w)Nr   r   r   z#Failed to parse yaml config from %s)
r   rj   r8   r   r   r{   r|   r\   r$   r   )rt   r   r   s      r   _load_yaml_config_filer   s  s       			  	!6!6!6!H!HII#FD119vvr9   :KHHH			s    AA2 2'BBc                     	 dd l }n"# t          $ r}t          d          |d }~ww xY w| j                            dd           |                     |                    |dd          d           d S )	Nr   z.PyYAML is required to write Hermes config.yamlTparentsexist_okF)rW   allow_unicoder   r   )r   rj   RuntimeErrorr7   mkdir
write_text	safe_dump)rt   rx   r   excs       r   _save_yaml_config_filer     s    V V V VKLLRUUV TD999uDII      s    
&!&r(   c                    g dt           t          z  dz  ddffd} ||            t          j        d          r |t          j        d                     t          dz  }t          dz  }|                                r ||           |                                r ||            ||            |t          dz             S )z8Return ordered candidate workspace paths, de-duplicated.	candidateNr   c                     | dv rd S 	 t          |                                                                           }n# t          $ r Y d S w xY w|vr                    |           d S d S )N)Nr
   )r   r2   r3   r\   r1   )r   r=   r9   s     r   addz"_workspace_candidates.<locals>.add  s    
""F		??--//7799DD 	 	 	FF	z!!d##### "!s   3= 
A
AHERMES_WEBUI_DEFAULT_WORKSPACE	workspacework)r4   r   r   r   r   r8   	STATE_DIR)r(   r   home_workspace	home_workr9   s       @r   _workspace_candidatesr     s    J$sTzD( $T $ $ $ $ $ $ CHHH	y122 9BI677888K'NvI N ICC	K   r   r=   c                 :   	 |                                                                  } |                     dd           |                                 o8t	          j        | t          j        t          j        z  t          j        z            S # t          $ r Y dS w xY w)zDBest-effort check that a workspace directory exists and is writable.Tr   F)
r2   r3   r   is_dirr   accessR_OKW_OKX_OKr\   )r=   s    r   _ensure_workspace_dirr     s      ((**

4$
///{{}}M4271BRW1L!M!MM   uus   B	B 
BBc                 j    t          |           D ]}t          |          r|c S t          d          )zBReturn the first usable workspace path, creating it when possible.zqCould not create or access any usable workspace directory. Set HERMES_WEBUI_DEFAULT_WORKSPACE to a writable path.)r   r   r   )r(   r   s     r   resolve_default_workspacer     sR    *3//  	 ++ 		
	A  r   c                      t                      S )z
    Resolve the default workspace in order:
      1. HERMES_WEBUI_DEFAULT_WORKSPACE env var
      2. ~/workspace if it already exists
      3. ~/work if it already exists
      4. ~/workspace (create if needed)
      5. STATE_DIR / workspace
    )r   rc   r   r   _discover_default_workspacer     s     %&&&r   HERMES_WEBUI_DEFAULT_MODELc                     d} d}d}ddddt            dt          rt          nd	 d
t          r| n| dt           dt           dt           dt
           dt           dt                       d
t                                                      rdnd dg}t          d
                    |          d           t          st          | dd           dS dS )zNPrint detected configuration at startup so the user can verify what was found.z[32m[ok][0mz[33m[!!][0mz[31m[XX][0mr
   z!  Hermes Web UI -- startup configz"  --------------------------------z  repo root   : z  agent dir   : z	NOT FOUNDz  z  python      : z  state dir   : z  workspace   : z  host:port   : rV   z  config file : z(found)z(not found, using defaults)
T)flusha\    Could not find the Hermes agent directory.
      The server will start but agent features will not work.

      To fix, set one of:
        export HERMES_WEBUI_AGENT_DIR=/path/to/hermes-agent
        export HERMES_HOME=/path/to/.hermes

      Or clone hermes-agent as a sibling of this repo:
        git clone <hermes-agent-repo> ../hermes-agent
N)r6   
_AGENT_DIR
PYTHON_EXEr   DEFAULT_WORKSPACEHOSTPORTrl   r8   printjoin_HERMES_FOUND)okwarnerrliness       r   print_startup_configr     s1   	B D
C 	+,&9&&cD::ccjHa^acc':''&9&&.,..(4(($((~+--~~>N>P>P>W>W>Y>Y1|_|~~
E 
$))E

$'''' 
 F F F 	
 	
 	
 	
 	
 	

 
r   c                      dg} g }i }| D ]Z}	 t          |           # t          $ r;}|                    |           t          |          j         d| ||<   Y d}~Sd}~ww xY wt          |          dk    ||fS )zw
    Attempt to import the key Hermes modules.
    Returns (ok: bool, missing: list[str], errors: dict[str, str]).
    	run_agentz: Nr   )
__import__r\   r1   type__name__len)requiredmissingerrorsmodes        r   verify_hermes_importsr     s    
 }HGF 5 5	5sOOOO 	5 	5 	5NN3 "!WW-4444F3KKKKKK		5
 LLA//s   
A"1AA"i@ HERMES_WEBUI_MAX_UPLOAD_MB   >   .bmp.gif.ico.jpg.png.svg.jpeg.webp>   .md.mdown	.markdown>   .c.h.go.js.py.rs.sh.ts.cpp.css.csv.jsx.log.sql.tsx.txt.xml.yml.bash.java.toml.yaml.env.html.jsonr   z	image/pngr   z
image/jpegr   r   z	image/gifr   zimage/svg+xmlr   z
image/webpr   zimage/x-iconr   z	image/bmpz.pdfzapplication/pdfr   application/jsonr   z	text/htmlz.htmz.xlszapplication/vnd.ms-excelz.xlsxzAapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheetz.doczapplication/mswordz.docxzGapplication/vnd.openxmlformats-officedocument.wordprocessingml.documentz.mp3z
audio/mpegz	audio/wavz	audio/mp4z	audio/aacz	audio/oggz
audio/opusz
audio/flacz	video/mp4zvideo/quicktimez
video/webmz	video/ogg)z.wavz.m4az.aacz.oggz.ogaz.opusz.flacz.mp4z.movz.m4vz.webmz.ogv)browserclarifycode_executioncronjob
delegationfile	image_genmemorysession_searchskillsterminaltodowebwebhookr   )z
hermes-clizhermes-api-serverc                     g }t                      }| pg D ]S}t                              ||f          }|D ]2}|r.||vr*|                    |           |                    |           3T|S )zGExpand legacy CLI toolset aliases while preserving order and de-duping.)set_LEGACY_CLI_TOOLSET_ALIASESr}   r   r1   )toolsetsr   seenr   replacementsreplacements         r   _normalize_cli_toolsetsr  p  s    J55DB / /266tdWEE' 	/ 	/K /{$66%%%!!+...	/ r   c                     | t                      } 	 ddlm} t           || d                    S # t          $ r? t          |                     di                               dt                              cY S w xY w)zResolve CLI toolsets using the agent's _get_platform_tools() so that
    MCP server toolsets are automatically included, matching CLI behaviour.Nr   )_get_platform_toolscliplatform_toolsets)rw   hermes_cli.tools_configr  r  r\   r}   _DEFAULT_TOOLSETS)ra   r  s     r   _resolve_cli_toolsetsr  }  s     {llg??????&':':3'F'FGGG g g g&sww/BB'G'G'K'KESd'e'efffffgs   1 AA:9A:OpenAIzopenai/gpt-5.4-minizGPT-5.4 Mini)provideridlabelzopenai/gpt-5.4zGPT-5.4	Anthropiczanthropic/claude-opus-4.7zClaude Opus 4.7zanthropic/claude-opus-4.6zClaude Opus 4.6zanthropic/claude-sonnet-4.6zClaude Sonnet 4.6zanthropic/claude-sonnet-4-5zClaude Sonnet 4.5zanthropic/claude-haiku-4-5zClaude Haiku 4.5Googlezgoogle/gemini-3.1-pro-previewzGemini 3.1 Pro Previewzgoogle/gemini-3-flash-previewzGemini 3 Flash Previewz$google/gemini-3.1-flash-lite-previewzGemini 3.1 Flash Lite Previewzgoogle/gemini-2.5-prozGemini 2.5 Prozgoogle/gemini-2.5-flashzGemini 2.5 FlashDeepSeekzdeepseek/deepseek-v4-flashzDeepSeek V4 Flashzdeepseek/deepseek-v4-prozDeepSeek V4 Prozdeepseek/deepseek-chat-v3-0324zDeepSeek V3 (legacy)zdeepseek/deepseek-r1zDeepSeek R1 (legacy)Qwenzqwen/qwen3-coderzQwen3 Coderzqwen/qwen3.6-pluszQwen3.6 PlusxAIzx-ai/grok-4.20z	Grok 4.20Mistralzmistralai/mistral-large-latestzMistral LargeMiniMaxzminimax/MiniMax-M2.7zMiniMax M2.7zminimax/MiniMax-M2.7-highspeedzMiniMax M2.7 HighspeedzZ.AIzzai/glm-5.1zGLM-5.1z	zai/glm-5zGLM-5zzai/glm-5-turbozGLM-5 Turbozzai/glm-4.7zGLM-4.7zzai/glm-4.5zGLM-4.5zzai/glm-4.5-flashzGLM-4.5 Flash
OpenRouterzopenrouter/elephant-alphazElephant Alpha (free)zopenrouter/owl-alphazOwl Alpha (free)ztencent/hy3-preview:freezHy3 Preview (free)z&nvidia/nemotron-3-super-120b-a12b:freezNemotron 3 Super (free)z#arcee-ai/trinity-large-preview:freezTrinity Large Preview (free)nouszNous Portal
openrouter	anthropicopenaiopenai-codexzOpenAI Codex	xai-oauthzxAI Grok OAuthcopilotzGitHub Copilot
cursor-acpz
Cursor ACPzaiz
Z.AI / GLMkimi-codingzKimi / Moonshotdeepseekminimax
minimax-cnzMiniMax (China)googlez
meta-llamaz
Meta LlamahuggingfaceHuggingFacealibabaAlibabaOllamazOllama CloudzOpenCode ZenzOpenCode Goz	LM Studioz
NVIDIA NIMXiaomizAWS Bedrock)ollamaollama-cloudopencode-zenopencode-golmstudio	mistralaiqwenx-ainvidiaxiaomibedrockglmz-aizz.aizhipugithubgithub-copilotzgithub-modelszgithub-modelgeminizgoogle-geminizgoogle-ai-studiokimimoonshotclaudezclaude-codez	deep-seekzminimax-china
minimax_cnopencoder;  grokxair@  zx.aiawsrC  zaws-bedrockamazonzamazon-bedrockr?  aliyun	dashscopezalibaba-cloudnimrA  z
nvidia-nimzbuild-nvidianemotronrB  custom)mimozxiaomi-mimolocalc                     | s| S t          |                                                                           }	 ddlm} ||v r||         S n# t
          $ r Y nw xY wt                              ||           S )a"  Return the canonical provider slug for *name*.

    Applies the WebUI's local alias table first, then merges any
    additional aliases the agent provides (when hermes_cli is on
    sys.path). Lookup is case-insensitive and whitespace-trimmed.
    Unknown names pass through unchanged.
    r   )_PROVIDER_ALIASES)r4   r   r   hermes_cli.modelsr[  r\   r}   )r   r(   _agent_aliasess      r   _resolve_provider_aliasr^    s      
d))//


!
!
#
#CIIIIII.  !#&& !     d+++s   A 
AAc                 2   t          | pd                                                                          }|sdS |                    d          r|S t	          j        dd|                              d          }t	          j        dd|          }|sdS d|z   S )Nr
   custom:z[^a-z0-9._-]+-z-{2,})r4   r   r   
startswithr!   sub)r   r(   slugs      r   _custom_provider_slug_from_namere  0  s    
djb//


!
!
'
'
)
)C r
~~i   
 6"C--33C88D6(C&&D rtr   
config_objc                     t          | t                    r| nt          }|                    dg           }t          |t                    sg S d |D             S )Ncustom_providersc                 <    g | ]}t          |t                    |S rc   )r{   r|   .0entrys     r   
<listcomp>z,_custom_provider_entries.<locals>.<listcomp>E  s'    BBBe*UD*A*ABEBBBr   )r{   r|   ra   r}   list)rf  sourceentriess      r   _custom_provider_entriesrq  @  sY    %j$77@ZZSFjj+R00Ggt$$ 	BBwBBBBr   c                 H    d d t          |           D             D             S )Nc                     h | ]}||S rc   rc   )rk  rd  s     r   	<setcomp>z/_named_custom_provider_slugs.<locals>.<setcomp>I  s0          r   c              3   Z   K   | ]&}t          |                    d                     V  'dS )r   N)re  r}   rj  s     r   	<genexpr>z/_named_custom_provider_slugs.<locals>.<genexpr>K  sJ       
 
 ,EIIf,=,=>>
 
 
 
 
 
r   )rq  )rf  s    r   _named_custom_provider_slugsrw  H  sD     
 
1*==
 
 
   r   r  c                    t          | pd                                                                          }|sdS |                    d          }t	          |          D ]}t          |                    d          pd                                                                          }t          |          }|r|s^|||hv s||                    d          k    r|c S dS )Nr
   r`  r   )r4   r   r   removeprefixrq  r}   re  )r  rf  r(   
raw_suffixrl  
entry_namerd  s          r   (_named_custom_provider_slug_for_providerr|  S  s     hn"


#
#
%
%
+
+
-
-C r!!),,J)*55  6**0b117799??AA
.z:: 	 	:t$$$
d6G6G	6R6R(R(RKKK )S2r   base_urlresolve_aliasr~  r  c                   t          | |          }|r|S |sbt          | pd                                                                          }|r|dk    rt	          ||          }|r|S t          | pd          S t          |           }|rMt          |pd                                                                          dk    rt	          ||          }|r|S |S )a  Normalize a configured provider id.

    When ``resolve_alias`` is True (default, used for active-provider /
    badge surfaces), falls through to ``_resolve_provider_alias`` after the
    named-custom check. When False (used by ``resolve_model_provider``),
    preserves the raw provider value so downstream local-server detection
    (`_LOCAL_SERVER_PROVIDERS` membership in #1625) sees the original name
    like ``ollama`` / ``lm-studio`` rather than alias-collapsed ``custom`` /
    ``lmstudio``. The base-url-to-named-slug fallback still runs in both
    modes when applicable.

    See in-stage absorption note on stage-313 for the #1625 regression that
    motivated the ``resolve_alias`` flag.
    r
   rW  )r|  r4   r   r   (_named_custom_provider_slug_for_base_urlr^  )r  rf  r~  r  
named_slugr(   by_base_urlresolveds           r   _resolve_configured_provider_idr  e  s    * :(JOOJ  #(.b!!''))//11 	#xB8ZXXK #""8>r"""&x00HB%%''--//8;;>xTT 	Or   c                 L   | sdS t          |                                                                                               dd          }|sdS |t          v s	|t
          v r|S t          |          }|r/|                                t          v r|                                S |S )uC  Normalise a provider id slug into a stable lowercase-hyphenated form.

    Folds underscores to hyphens and lowercases the result, so a user with
    ``providers.opencode_go.api_key`` in ``config.yaml`` and
    ``model.provider: opencode-go`` sees ONE provider group, not two
    (#1568). Then attempts alias resolution but only if the alias target
    is itself a known canonical id in ``_PROVIDER_DISPLAY`` —  this avoids
    converting ``x-ai`` (canonical in WebUI's data structures) to ``xai``
    (the hermes_cli alias target which the WebUI doesn't index by).

    Examples::

        opencode-go     -> opencode-go     (canonical, no change)
        opencode_go     -> opencode-go     (underscore folded)
        OpenCode-Go     -> opencode-go     (case folded)
        OPENCODE_GO     -> opencode-go     (both folded)
        z_ai            -> zai             (alias-resolved — zai is canonical)
        x-ai            -> x-ai            (preserved — x-ai is canonical)

    Empty input passes through as the empty string. Unknown ids preserve
    their normalised form.
    r
   _ra  )r4   r   r   replace_PROVIDER_DISPLAY_PROVIDER_MODELSr^  )r   r(   r  s      r   _canonicalise_provider_idr    s    .  r
d))//


!
!
#
#
+
+C
5
5C r 3*:#:#:

 's++H  HNN$$(999~~Jr   valuec                    t          | pd                                                              d          }|sdS t          d|v r|nd|           }|j        pd                                }|j        p|j                                                            d          }|j                            d          }|j        sd}| d| | S )Nr
   /://http://http)r4   r   rstripr   schemer   netlocr=   )r  url
parsed_urlr  r  r=   s         r   _normalize_base_url_for_matchr    s    
ekr


 
 
"
"
)
)#
.
.C r###?S??CCJ)60022F2:?99;;BB3GGF?!!#&&D ''''''r   c                    t          | pd                                                              d          }|st                      S t	          d|v r|nd|           }|j        pd                                                                }|st                      S |j        }|%|j        pd                                }|dk    rdnd	}d
| d| d
| d| hS )a  Return custom provider slugs that WebUI may derive from a base URL.

    Model picker values for endpoint-discovered models have historically used
    both ``custom:<host>:<port>`` and ``custom:<host>-<port>`` forms. When the
    active config already names a local-server provider such as Ollama for that
    same base URL, those endpoint slugs are just UI routing hints and should
    resolve back to the configured provider rather than requiring a CUSTOM_* API
    key.
    r
   r  r  r  Nr  httpsi  P   r`  rV   ra  )	r4   r   r  r  r   hostnamer   portr  )r  r  r  hostr  r  s         r   #_custom_endpoint_slugs_for_base_urlr    s     ekr


 
 
"
"
)
)#
.
.C uu###?S??CCJ%2,,..4466D uu?D|#-v4466''ssR#d##T##%<t%<%<d%<%<==r   !_LEGACY_CUSTOM_API_KEY_ENV_WARNEDprovider_idc                     t          j        ddt          | pd                                                                        d          }|sd}|                    d          sd| }| dS )zGReturn the POSIX-safe default API-key env var for a custom provider id.z[^A-Za-z0-9]r  r
   CUSTOMCUSTOM__API_KEY)r!   rc  r4   upperr   rb  )r  	sanitizeds     r   _api_key_env_namer    s|    S1B-C-CDDJJLLRRSVWWI 		** *)i))	!!!!r   c                 ~    t          | pd                                                                          }|sdS | dS )z<Return the pre-#2541 custom-provider env hint shape, if any.r
   r  )r4   r   r  )r  r(   s     r   _legacy_custom_api_key_env_namer    sH    
kR
 
 
&
&
(
(
.
.
0
0C rr   c                 |   t          |           }t          j        |d                                          }|r|S t	          |           }|rp||k    rjt          j        |d                                          }|rA|t
          vr6t
                              |           t                              d||           |S dS )zFLook up sanitized custom-provider env first, then legacy broken shape.r
   z@Custom provider API key env var %s is deprecated; use %s insteadN)	r  r   r   r   r  r  r   r$   r%   )r  env_nameapi_keylegacy_env_name
legacy_keys        r   _lookup_custom_api_key_envr    s     --Hi"%%++--G 5kBBO 
?h66Y3399;;
 	&GGG155oFFFV#  
 4r   c                     t          |           }|sdS t          |          D ]P}t          |                    d                    }||k    r+t          |                    d                    pdc S dS )Nr
   r~  r   rW  )r  rq  r}   re  )r~  rf  targetrl  entry_base_urls        r   r  r    s     +844F r)*55 N N6uyy7L7LMMV##.uyy/@/@AAMXMMM2r   zclaude-opus-4.7r  r  zclaude-opus-4.6zclaude-sonnet-4.6zclaude-sonnet-4-5zclaude-haiku-4-5zgpt-5.5zGPT-5.5zgpt-5.5-minizGPT-5.5 Minizgpt-5.4-minizgpt-5.4zgpt-5.3-codexzGPT-5.3 Codexzgpt-5.2-codexzGPT-5.2 Codexzgpt-5.1-codex-maxzGPT-5.1 Codex Maxzgpt-5.1-codex-minizGPT-5.1 Codex Minizcodex-mini-latestzCodex Mini (latest)zgemini-3.1-pro-previewzgemini-3-flash-previewzgemini-3.1-flash-lite-previewzgemini-2.5-prozgemini-2.5-flashzdeepseek-v4-flashzdeepseek-v4-prozdeepseek-chat-v3-0324zdeepseek-reasonerzDeepSeek Reasoner (legacy)z@nous:anthropic/claude-opus-4.6zClaude Opus 4.6 (via Nous)z!@nous:anthropic/claude-sonnet-4.6zClaude Sonnet 4.6 (via Nous)z@nous:openai/gpt-5.4-minizGPT-5.4 Mini (via Nous)z#@nous:google/gemini-3.1-pro-previewz!Gemini 3.1 Pro Preview (via Nous)zglm-5.1zglm-5zglm-5-turbozglm-4.7zglm-4.5zglm-4.5-flashzmoonshot-v1-8kzMoonshot v1 8kzmoonshot-v1-32kzMoonshot v1 32kzmoonshot-v1-128kzMoonshot v1 128kzkimi-latestzKimi Latestz	kimi-k2.5z	Kimi K2.5zMiniMax-M2.7zMiniMax-M2.7-highspeedzMiniMax-M2.5zMiniMax M2.5zMiniMax-M2.5-highspeedzMiniMax M2.5 HighspeedzMiniMax-M2.1zMiniMax M2.1z
MiniMax-M2z
MiniMax M2zgpt-4ozGPT-4ozcursor/composer-2.5zComposer 2.5zcursor/composer-2z
Composer 2zcursor/defaultDefaultzgpt-5.4-prozGPT-5.4 Prozgpt-5.4-nanozGPT-5.4 Nanozgpt-5.3-codex-sparkzGPT-5.3 Codex Sparkzgpt-5.2zGPT-5.2zgpt-5.1zGPT-5.1zgpt-5.1-codexzGPT-5.1 Codexgpt-5zGPT-5zgpt-5-codexzGPT-5 Codexz
gpt-5-nanoz
GPT-5 Nanozclaude-opus-4-7zclaude-opus-4-6zclaude-opus-4-5zClaude Opus 4.5zclaude-opus-4-1zClaude Opus 4.1zclaude-sonnet-4-6zclaude-sonnet-4zClaude Sonnet 4zclaude-3-5-haikuzClaude 3.5 Haikuzminimax-m2.5zminimax-m2.5-freezMiniMax M2.5 Freeznemotron-3-super-freezNemotron 3 Super Freez
big-picklez
Big Pickler<  z	kimi-k2.6z	Kimi K2.6zmimo-v2-prozMiMo V2 Prozmimo-v2-omnizMiMo V2 Omnizmimo-v2.5-prozMiMo V2.5 Proz	mimo-v2.5z	MiMo V2.5zminimax-m2.7zqwen3.6-pluszqwen3.5-pluszQwen3.5 Plusr>  zmistral-large-latestzmistral-small-latestzMistral Smallzqwen3-coderz!nvidia/nemotron-3-super-120b-a12bzNemotron 3 Super 120Bznvidia/nemotron-3-nano-30b-a3bzNemotron 3 Nano 30Bz(nvidia/llama-3.3-nemotron-super-49b-v1.5zLlama 3.3 Nemotron Super 49Bz qwen/qwen3-next-80b-a3b-instructzQwen3 Next 80Bzmimo-v2-flashzMiMo V2 Flashz	grok-4.20z global.anthropic.claude-opus-4-7z Global Anthropic Claude Opus 4.7z#global.anthropic.claude-opus-4-6-v1z Global Anthropic Claude Opus 4.6z"global.anthropic.claude-sonnet-4-6z"Global Anthropic Claude Sonnet 4.6z.global.anthropic.claude-opus-4-5-20251101-v1:0z GLOBAL Anthropic Claude Opus 4.5z0global.anthropic.claude-sonnet-4-5-20250929-v1:0zGlobal Claude Sonnet 4.5z/global.anthropic.claude-haiku-4-5-20251001-v1:0z!Global Anthropic Claude Haiku 4.5)rA  rB  r@  r*  rC  gh_cligh auth tokenro  r  
key_sourcec                    |                                                                  t          v pS|                                                                 dk    p)|                                                                 dk    S )zTrue when a credential-pool entry is a seeded gh-cli token rather than
    one the user added explicitly. Filter these so Copilot doesn't appear in
    the dropdown just because `gh` is installed on the system.
    r  )r   r   _AMBIENT_GH_CLI_MARKERS)ro  r  r  s      r   _is_ambient_gh_cli_entryr    sl     	"99 	9;;==  O3	9##%%8r   midc                     |                      d          \  }}}dt          dt          fd} ||          }|r|d ||           dz  }|S )u   Turn an Ollama model id (Ollama tag format) into a readable display label.

    Examples: 'kimi-k2.5' → 'Kimi K2.5', 'qwen3-vl:235b-instruct' → 'Qwen3 VL (235B Instruct)'
    rV   sr   c                    |                      dd                               dd                                          }g }|D ]}|                     dd          }|                                r;t          |          dk    r(|                    |                                           g|                                rD|rB|d                                         r(|                    |                                           |                    |r%|d                                         |dd          z   n|           d                    |          S )	Nra   r  .r
      r   r    )	r  splitisalphar   r1   r  isalnumisdigitr   )r  tokensoutt
alpha_onlys        r   _fmtz"_format_ollama_label.<locals>._fmt  s.   3$$,,S#66<<>> 	= 	=A3++J!!## =A!

17799%%%%##%% =* =A9N9N9P9P =

17799%%%%

1;1Q4::<<!ABB%//!<<<<xx}}r    ())	partitionr4   )r  	name_partr  variantr  r  s         r   _format_ollama_labelr    sx    
  MM#..Iq'      DOOE '&dd7mm&&&&Lr   c                     d| v r|                      dd          d         n| }|                                                    d          rd|t          d          d         z   }t	          |          }| dS )u  Turn a Nous Portal model id into a readable display label.

    Nous IDs are ``<vendor>/<model>[:<variant>]`` (e.g. ``anthropic/claude-opus-4.7``);
    drop the vendor namespace, prettify the model name with the same token
    rules as :func:`_format_ollama_label` (short acronyms uppercase, size
    suffixes uppercase, capitalize the rest), then append ``" (via Nous)"``
    so the entry is visually distinct from same-named models in other
    provider groups (e.g. direct Anthropic).

    Examples (matches the helper's actual output — labels are produced by
    :func:`_format_ollama_label`'s token rules, so 3-letter tokens like
    ``GPT`` and ``PRO`` render uppercase)::

        anthropic/claude-opus-4.7         -> Claude Opus 4.7 (via Nous)
        openai/gpt-5.4-mini               -> GPT 5.4 Mini (via Nous)
        google/gemini-3.1-pro-preview     -> Gemini 3.1 PRO Preview (via Nous)
        moonshotai/kimi-k2.6              -> Kimi K2.6 (via Nous)
        qwen/qwen3.5-plus-02-15           -> Qwen3.5 Plus 02 15 (via Nous)
        nvidia/nemotron-3-super-120b-a12b -> Nemotron 3 Super 120B A12b (via Nous)
        minimax/minimax-m2.5:free         -> MiniMax M2.5 (Free) (via Nous)
    r  r    r0  r#  Nz (via Nous))r  r   rb  r   r  )r  r  bases      r   _format_nous_labelr    s    , *-		#q!!"%%I ##I.. ;	#i..// ::		**Dr         )r'  r(  r2  
moonshotairE  r0  r?  r@  r/  stepfunrB  tencentrA  zarcee-ai)selected_model_idr  live_idsr  r  c                D   | sg g fS t          |           t          k    rt          |           g fS g t                      dt          ddffd}|r=|}|                    d          r|t          d          d         }|| v r ||           t                              dg           D ]S}|                    dd          }|                    d          r|t          d          d         }|| v r ||           Ti }| D ]R}|v rd	|v r|                    d	d
          d         nd}	|	                    |	g           
                    |           St          t                    t          fd|D                       }
|
z   }t                    |k     rqd}|D ]R}	t                    |k    r n<|                    |	          }|s/ ||                    d                     |d
z  }S|dk    rnt                    |k     qfd| D             }|fS )u	  Trim a Nous Portal catalog into a (featured, extras) split.

    ``featured`` is what the picker dropdown renders. ``extras`` is everything
    else — kept available so the slash-command `/model` autocomplete and the
    ``_dynamicModelLabels`` map cover the full catalog.

    Selection rules (in order, deterministic):

    1. Always include the user's currently-selected model if it's in the
       catalog (preserves selection stickiness — no orphan IDs in the
       dropdown after a refresh).
    2. Always include every entry from the curated static
       ``_PROVIDER_MODELS["nous"]`` list whose id maps onto a live id —
       those four are explicitly maintained as flagship picks.
    3. Top up to ``target`` by walking ``_NOUS_VENDOR_PRIORITY`` round-robin
       (one model per vendor each pass) so no vendor monopolises the slot
       budget. Within a vendor, the original ``live_ids`` order is preserved
       — that's the order Nous Portal returned, which approximates recency.

    Returns ``(featured_ids, extras_ids)`` — both lists are subsets of
    ``live_ids`` with disjoint membership and union equal to ``live_ids``.

    For catalogs ≤ ``_NOUS_FEATURED_THRESHOLD`` entries the function is a
    no-op: ``featured == live_ids``, ``extras == []``.
    r  r   Nc                 p    | r0| vr.                     |                                |            d S d S d S N)r1   r   )r  chosen
chosen_sets    r   _addz&_build_nous_featured_set.<locals>._addi  sO     	 3j((MM#NN3	  	 ((r   @nous:r%  r  r
   r  r    r   c              3   >   K   | ]}|t                    v|V  d S r  )r  )rk  vprioritys     r   rv  z+_build_nous_featured_set.<locals>.<genexpr>  s3      EEAas8}}.D.Da.D.D.D.DEEr   c                     g | ]}|v|	S rc   rc   )rk  r)   r  s     r   rm  z,_build_nous_featured_set.<locals>.<listcomp>  s#    999AQj%8%8a%8%8%8r   )r   _NOUS_FEATURED_THRESHOLDrn  r  r4   rb  r  r}   r  
setdefaultr1   _NOUS_VENDOR_PRIORITYsortedpop)r  r  r  r  selstaticsid	by_vendorr  vendorleftovervendor_orderadded_this_passbucketextrasr  r  r  s                  @@@r   _build_nous_featured_setr  B  s   >  2v
8}}000H~~r!!F55J #  $                >>(## 	&c(mmnn%C(??DIII #&&vr22  jjr"">>(## 	&c(mmnn%C(??DIII ')I 5 5*),3""1%%VR((//4444 )**HEEEEEEEEEHh&L f++

" 	! 	!F6{{f$$]]6**F DAq OOa f++

 :999999F6>r   
raw_modelsactive_providerc                 6   |pd                                 }|r||k    rt          |           S g }| D ]f}|d         }|                    d          sd|v r|                    ||d         d           B|                    d| d| |d         d           g|S )zReturn *raw_models* with @provider: prefixes applied when needed.

    Prefixing is skipped when (a) the provider is already the active one, or
    (b) a model id already starts with '@' or contains '/' (already routable).
    r
   r  @r  r  r  rV   )r   rn  rb  r1   )r  r  r  _activeresultr)   r  s          r   _apply_provider_prefixr    s     $"++--G  kW,,JF P Pg>># 	P#**MMqz::;;;;MM!8[!8!83!8!81W:NNOOOOMr   groupsc                 R     sdS t          t          t                                fd          }i }|D ]} |         }t          |                    dg                     D ]\  }}t          |                    dd          pd                                          }|r|                    d          rT|                    |g           	                    ||f           |
                                D ]\  }}	t          |	          dk     r|	d	d         D ]\  }} |         }|d         |         }|                    d
d          }
d|
 d| |d<   |                    d|
          }|                    d          |k    r|d          d| d|d<   }| d| d|d<   dS )ub  Ensure every model ID across groups is globally unique.

    When multiple providers expose the same model ID (either bare names like
    ``gpt-5.4`` or slash-qualified IDs like ``google/gemma-4-27b``), the
    dropdown cannot distinguish them. This post-process detects such
    collisions and prefixes colliding entries with ``@provider_id:`` so the
    frontend can treat them as distinct options.

    The first occurrence (in provider-id order) is left unchanged for backward
    compatibility with sessions that already store the original bare/slash
    model name. If that provider is later removed from the config, the next
    cache rebuild re-runs dedup — the remaining provider becomes the sole
    occurrence and is left unchanged, so the session still matches.

    .. note::
       The "first occurrence wins" rule means the unchanged ID is not stable
       across config changes (adding, removing, or reordering providers).
       This is acceptable because the dedup runs on every cache rebuild,
       so sessions always resolve to the current canonical unchanged ID.

    The ``@provider_id:model`` format is consistent with the existing
    ``_apply_provider_prefix()`` function and is handled by
    ``resolve_model_provider()`` (rsplits on the last ``:`` to handle
    provider_ids that themselves contain ``:``).

    Operates in-place on *groups*.
    Nc                 <    |                               dd          S )Nr  r
   r}   )ir  s    r   <lambda>z(_deduplicate_model_ids.<locals>.<lambda>  s    fQimmM266 r   keymodelsr  r
   r     r    r  rV   r  r  r  r  )r  ranger   	enumerater}   r4   r   rb  r  r1   items)r  sorted_group_indicesid_mapgir'   mimodelr  original_id	locationspidprovider_names   `           r   _deduplicate_model_idsr    s   8  
 "c&kk6666   02F" 8 8r
"599Xr#:#:;; 	8 	8IBeiib))/R006688C #..-- c2&&--r2h7777	8 #),,.. D DYy>>Am 		D 		DFB2JE(OB'E))M2..C1c11K11E$K!IIj#66Myy!![00$)'N!F!Fm!F!F!Fg$/!C!C=!C!C!Cg		DD Dr   >   	llama-cppvllmtabbyr9  localaitextgenllamacppr=  tabbyapi	koboldcpp	lm-studioc                     t          | pd                                                                          }|t          v rdS |                    d          r|                    d          t          v S dS )zTrue when provider_id names a local model server.

    Named custom providers resolve to ``custom:<slug>``. Treat those as local
    when the bare slug is one of the known local-server provider names too.
    r
   Tr`  F)r4   r   r   _LOCAL_SERVER_PROVIDERSrb  ry  )r  r  s     r   _is_local_server_providerr!    su     ;$"%%++--3355H***t9%% K$$Y//3JJJ5r   c                    | sdS 	 ddl m} ddl} ||           j        pd                                }|sdS |dv rdS 	 |                    |          }n# t          $ r Y dS w xY w|j        p|j        p|j	        S # t          $ r Y dS w xY w)uT  True if base_url's host is a loopback or private IP (likely local server).

    Reuses ipaddress.is_loopback / is_private / is_link_local — the same
    heuristic used in the `api/config.py` SSRF/credential-routing code.
    Errors (DNS failure, malformed URL) return False so callers fall back to
    the static-provider-name check.
    Fr   )r   Nr
   )	localhostzip6-localhostzip6-loopbackT)urllib.parser   	ipaddressr  r   
ip_address
ValueErroris_loopback
is_privateis_link_localr\   )r~  r   r%  r  addrs        r    _base_url_points_at_local_serverr,  +  s      u))))))""+1r88:: 	5AAA4	''--DD 	 	 	 55		
 H4?Hd6HH   uus9   0A; A; A A; 
A#A; "A##A; ;
B	B	restc                    t          | pd                                          } d| vrdS |                     dd          \  }}|rd|v rdS |                                sdS 	 t	          |          }n# t
          $ r Y dS w xY wd|cxk    rdk    sn dS 	 ddl}|                    |           dS # t
          $ r Y nw xY w|                                }|d	k    rdS d
|v rdS dS )uR  True when ``custom:<rest>`` is an endpoint-style slug ``host:port``.

    WebUI sometimes derives ``custom:10.8.71.41:8080`` from ``base_url`` authority.
    The #1776 peel must not treat that middle colon as part of an eaten model
    segment — otherwise ``@custom:10.8.71.41:8080:Qwen3`` wrongly becomes model
    ``8080:Qwen3``.
    r
   rV   Fr    i  r   NTr#  r  )	r4   r   rsplitr  r&   r'  r%  r&  r   )r-  r  port_sport_nr%  hls         r   &_custom_slug_rest_looks_like_host_portr3  J  sB    tzr??  ""D
$u;;sA&&LD& 3$;;u>> uV   uu    5    uT"""t   	B	[t
d{{t5s$   "A2 2
B ?B B0 0
B=<B=c                    t                               di                               | i           pi }|                    d          pd                                                    d          }|r|S t                               di           pi }t	          |t
                    rt          |                    d          pd                                                                          }|t          |                                                                           k    r@|                    d          pd                                                    d          }|r|S dS )u0  Look up the configured base_url for a provider (e.g. lmstudio).

    Checks two locations, in order:
      1. ``cfg["providers"][<provider_id>]["base_url"]`` — the explicit
         per-provider override.
      2. ``cfg["model"]["base_url"]`` — falls back here when
         ``cfg["model"]["provider"] == provider_id``. This is the historical
         shape (the model block carries both the active provider AND the
         base URL for that provider in a single record).

    Returns the URL stripped of trailing ``/`` if configured, otherwise None.
    	providersr~  r
   r  r  r  N)ra   r}   r   r  r{   r|   r4   r   )r  prov_cfgexplicit	model_cfgmodel_provider
model_bases         r   _get_provider_base_urlr;  p  s7    ww{B''++K<<BHZ((.B5577>>sCCH $$*I)T"" "Y]]:66<"==CCEEKKMMS--3355;;====#--
339r@@BBII#NNJ "!!4r   model_idc                    d}d}t                               di           }t          |t                    r@|                    d          }t	          |                    d          t           |d          }t          |t
                    r,|                                                                dk    rd}| pd	                                } | s| ||fS |duo|dk    o|                    d
           }t          |t                    r|                    d          nd}t                      }|@|t          v r7t          t          |         t                    rd t          |         D             }|o|dur| |k    p| |v }t                               dg           }t          |t                    r9|s6|D ]2}	t          |	t                    s|	                    d          pd	                                }
|	                    d          pd	                                }|	                    d          pd	                                }t                      }|
r|                    |
           |	                    d          }t          |t                    r1|                    d |                                D                        |r| |v rt          |          }| ||pdfc S 4|                     d          r=d| v r8| dd         }|                    dd          \  }}|                    d
          r`|                    d          dk    rG|t%          d
          d         }t'          |          s |                    dd          \  }}| d| }n@|t          vr7|t(          vr.|                    d
          s|                    dd          \  }}|                    d
          r9|r7t-          |          r(|                                t/          |          v r|||fS ||t1          |          fS d| v r|                     dd          \  }}|dk    r| d|fS h d}||v r| ||fS |r||k    r|||fS |dk    rNt          |pd	                                                              d          dk    r|t          v r||k    r| ddfS |r|r||k    rt                               dg           }t          |t                    r|D ]}t          |t                    rm|                    dd	                                          |k    rAt          |          }|                    d          pd	                                }| ||pdfc S |r6t-          |          st5          |          r| ||fS |t          v r|||fS | ||fS |t          v r||k    r| ddfS | ||fS )a'  Resolve model name, provider, and base_url for AIAgent.

    Model IDs from the dropdown can be in several formats:
      - 'claude-sonnet-4.6'            (bare name, uses config default provider)
      - 'anthropic/claude-sonnet-4.6'  (OpenRouter-style provider/model)
      - '@minimax:MiniMax-M2.7'        (explicit provider hint from dropdown)

    The @provider:model format is used for models from non-default provider
    groups in the dropdown, so we can route them through the correct provider
    via resolve_runtime_provider(requested=provider) instead of the default.

    Custom OpenAI-compatible endpoints are special: their model IDs often look
    like provider/model (for example ``google/gemma-4-26b-a4b``), which would be
    mistaken for an OpenRouter model if we only looked at the slash. To avoid
    that, first check whether the selected model matches an entry in
    config.yaml -> custom_providers and route it through that named custom
    provider.

    Returns (model, provider, base_url) where provider and base_url may be None.
    Nr  r~  r  Fr}  rY  rW  r
   r`  rY   c                     h | ]U}t          |t                    t          |                    d           t                    ?|                    d d          VS )r  r
   )r{   r|   r}   r4   rk  r)   s     r   rt  z)resolve_model_provider.<locals>.<setcomp>  s`      
  
  
 !!T"" 
'1!%%++s'C'C 
EE$OO 
  
  
r   rh  r   r  c              3      K   | ]A}t          |t                    |                                +|                                V  Bd S r  r{   r4   r   )rk  r  s     r   rv  z)resolve_model_provider.<locals>.<genexpr>  s`       ' '!#s++' 14		'IIKK' ' ' ' ' 'r   r  rV   r    r  r  r&  >   r%  rA  r<  r;  r)  z%https://chatgpt.com/backend-api/codex)ra   r}   r{   r|   r  r4   r   r   rb  r  r  rn  r   r   keysre  r/  countr   r3  r  r  r!  r  r;  r  r,  )r<  config_providerconfig_base_urlr8   _is_explicit_non_custom_provider_default_model_provider_models_set_skip_custom_providersrh  rl  entry_modelr{  r  entry_model_idsentry_modelsprovider_hintinner
bare_model
_slug_restextraprefixbare_PORTAL_PROVIDERS_custom_cfg_entry_slug_bases                              r   resolve_model_providerrY    s   * OO$$I)T"" 
#--
339MM*%%$	
 
 
 /3'' #O,A,A,C,C,I,I,K,Kw,V,V"B%%''H :/99 	t# 	6x'	6**9555 %
 2<It1L1LVY]]9---RVN%(UU#///'8$?? 0 
  
%5o%F 
  
  

 	) 	
 4'FH,F 0//  ww1266"D)) G2H G% 	G 	GEeT**  99W--3::<<K))F++1r88::J#ii
339r@@BBN!eeO 1##K000 99X..L,-- && ' '+0022' ' '   
  Gh/99 ?
 K K0F$FFFF. 3 PC8OO$)LLa$8$8!z##I.. 	<=3F3Fs3K3Kq3P3P&s9~~7J9*EE 5'4';';C'C'C$u %44
44
#333!):::%00;; ;(-C(;(;%M:$$Y//	@	@ */::	@ ##%%)L_)])]]]??=*@*O*OOO
h~~c1-- l**\?:: NMM///_o==  	:v88/99 ~--O)r**002299#>>67 7***/))\4//
  	>o 	>&O*C*C''"4b99K+t,, >) > >F!&$// >FJJvr4J4J4P4P4R4RV\4\4\ ? G G!'J!7!7!=2 D D F F'====
  	> */:: B7HHB/AA
 )))_o==_o==
 %%%&O*C*C\4//_o55r   c                 	   t          | pd                                                                          }|                    d          sdS dt           dt           fd} ||                    dd          d                                                   }|sdS t                      }ddt           d	z  fd
}|                    dg           }t          |t                    sg }|D ]}t          |t                    st          |                    d          pd                                          }|sQ ||          }	|	|k    rct          |                    d          pd                                          pd	}
 ||                    d          |                    d          |          }||
fc S t          |          dk    rt          |d         t                    rs|d         } ||                    d          |                    d          |          t          |                    d          pd                                          pd	fS |                    di           }t          |t                    r|                    |i           ni }t          |t                    r|                    di           ni }|                    di           }t          |t                    rHt          |                    d          pd                                                                          nd}d	}|||fD ]S}t          |t                    r<t          |                    d          pd                                          }|r|} nTd	}t          |t                    r3 ||                    d          |                    d          |          }|sHt          |t                    r3 ||                    d          |                    d          |          }|sOt          |t                    r:|d||hv r3 ||                    d          |                    d          |          }|s|r||pd	fS dS )a  Return (api_key, base_url) for a named ``custom:*`` provider.

    Supports ``custom_providers[].api_key`` as either a literal key or
    ``${ENV_VAR}``, and ``custom_providers[].key_env`` as an env-var hint.
    Returns ``(None, None)`` when no named custom provider matches.
    r
   r`  )NNr  r   c                 "   t          | pd                                                                                              dd                              dd          }d|v r|                    dd          }d|v |                    d          S )Nr
   r  ra  r  z--)r4   r   r   r  )r  r  s     r   _slugifyz4resolve_custom_provider_connection.<locals>._slugifyw  s    ""$$**,,44S#>>FFsCPPaii		$$$A aiiwws||r   rV   r    Nc                    d }| t          |                                           }|                    d          rZ|                    d          rEt	          |          dk    r2t          j        |dd         d                                          pd }n|r|}|sNt          |pd                                          }|r)t          j        |d                                          pd }|s|rt          |          }|S )Nz${}r  r  r  r
   )r4   r   rb  endswithr   r   r   r  )raw_api_keyraw_key_envrM  r  key_textkey_envs         r   _resolve_keyz8resolve_custom_provider_connection.<locals>._resolve_key  s   ";''--//H""4(( #X->->s-C-C #HXYHYHY)HQrTNB77==??G4 #" 	A++,,2244G A)GR006688@D 	@= 	@0??Gr   rh  r   r~  r  rc  r   r5  rW  r  r  r  )r4   r   r   rb  r  rw   r}   r{   rn  r|   r   )r  r  r\  rd  cfg_datard  rh  rl  r   
entry_slugr~  r  providers_cfgprovider_specificprovider_customr8  r9  fallback_baser   rX  fallback_keys                        r   "resolve_custom_provider_connectionrl  l  s    kR
 
 
&
&
(
(
.
.
0
0C>>)$$ z      8CIIc1%%a(..0011D z ||H cDj       ||$6;;&-- ! ! !%&& 	599V$$*++1133 	Xd^^
uyy,,23399;;Ct,uyy33UYYy5I5I3OO     !!j1A!1Dd&K&K! #L9--uyy/C/CSII		*%%+,,2244<
 	
 LLb11M6@PT6U6U]))#r222[]9CMSW9X9X`m''"555^`OWb))IMWXacgMhMhpSz228b99??AAGGIIInpNM')D  	i&& 		j117R88>>@@E  %L#T** m#|$5$9$9)$D$DFWF[F[\eFfFfhkll iJ== i#|O$7$7	$B$BODWDWXaDbDbdghh ]Jy$77 ]NxY\^bNc<c<c#|IMM)$<$<immI>V>VX[\\ 3} 3]2d22:r   copilot-acpr9  c                 R   t          | pd                                          }t          |pd                                                                          }|r|r|dk    s|                    d          r|S t                              di           }d}t          |t                    rHt          |                    d          pd                                                                          }|t          v rd| d| S ||k    r|S |dk    rd| d| S d	|v r|S d| d| S )
a  Return the model string to pass to ``resolve_model_provider()``.

    Session persistence keeps the user's selected provider in ``model_provider``
    instead of forcing every selected model into ``@provider:model`` form. At
    runtime, however, ``resolve_model_provider()`` still understands that
    internal disambiguation form, so use it only when the provider context is
    needed to route away from the current default provider.
    r
   rY   r  r  Nr  rV   r&  r  )	r4   r   r   rb  ra   r}   r{   r|   _ACP_SUBPROCESS_PROVIDERS)r<  r9  r  r  r8  rD  s         r   model_with_provider_contextrp    s\    B%%''E>'R((..006688H  H	$9$9U=M=Mc=R=R$9$$IO)T"" OimmJ77=2>>DDFFLLNN ,,,%8%%e%%% ?"" <%8%%e%%% e||!x!!%!!!r   c                    | | nt           }t          }|                    di           }t          |t                    r|                                }nOt          |t                    r:t	          |                    d          pd                                          }|r|}t          j        d          p't          j        d          pt          j        d          }|r|                                }|S )zKResolve the effective Hermes default model from config, then env overrides.Nr  rY   r
   HERMES_MODELOPENAI_MODEL	LLM_MODEL)	ra   DEFAULT_MODELr}   r{   r4   r   r|   r   r   )rx   r   default_modelr8  cfg_default	env_models         r   get_effective_default_modelry    s     + 7SJ!Mw++I)S!! (!))	It	$	$ ()--	228b99??AA 	('M 		.!!XRY~%>%>X")KBXBX   *!))r   )minimallowmediumhighxhighc                     | r!t          |                                           sdS t          |                                                                           }|dk    rddiS |t          v rd|dS dS )a"  Parse an effort level into the dict the agent expects.

    Returns None when *effort* is empty or unrecognised (caller interprets as
    "use default"), ``{"enabled": False}`` for ``"none"``, and
    ``{"enabled": True, "effort": <level>}`` for any of
    ``VALID_REASONING_EFFORTS``.
    NnoneenabledFT)r  effort)r4   r   r   VALID_REASONING_EFFORTS)r  effs     r   parse_reasoning_effortr    s      V**,, t
f++




#
#
%
%C
f}}5!!
%%%3///4r   c                     t          | pd                                          }|                    d          r d|v r|                    dd          d         S |S )zFRemove WebUI routing hints before provider-specific capability lookup.r
   r  rV   r    )r4   r   rb  r  )r<  r  s     r   "_strip_provider_hint_for_reasoningr  +  s[    B%%''E &{{3""1%%Lr   c                    t          |                                           t          t          |pd                                                                                    }r|dv rg S                     dd          d         }|dk    rB|                    d          r-|                    d          rg d	S t          t                    S |d
v rB|                    d          r-|                    d          rg d	S t          t                    S d}t          fd|D                       rt          t                    S g S )z(Fallback when hermes_cli is unavailable.r
      r,  rm  r  r    r  r)  )r  o1o3o4)r  r  r  )r{  r|  r}     r+  rH  	z	deepseek/z
anthropic/zopenai/zx-ai/zgoogle/gemini-2zgoogle/gemma-4z
qwen/qwen3ztencent/hy3-previewzxiaomi/c              3   B   K   | ]}                     |          V  d S r  rb  )rk  rR  r  s     r   rv  z/_heuristic_reasoning_efforts.<locals>.<genexpr>N  s1      
;
;5F##
;
;
;
;
;
;r   )
r  r   r^  r4   r   r/  rb  rn  r  any)r<  r  r  rS  prefixesr  s        @r   _heuristic_reasoning_effortsr  3  s^   .x88>>@@E&s;+<"'='='C'C'E'E'K'K'M'MNNH H ===	<<Q#D>!!doo6Q&R&R!??-.. 	-,,,,+,,,000??677 	1122 10000/000
H 
;
;
;
;(
;
;
;;; -+,,,Ir   c                 (   t          | pd                                          }|sg S |r5t          |pd                                                                          nd}t          |pd                                          pd}|s	 t          |          \  }}}nr# t          $ re t          t
                              d          pi                     d          pd                                                                          }Y nw xY wt          |          }|dv rg S 	 ddlm	}m
} n # t          $ r t          ||          cY S w xY wt          |          }	|dv r ||	          S |d	k    r'|	                    d
d          d         }
 ||
          S |dk    r|pt          |          } |||          }d |D             }|r#t          |                              dh          rg S d |D             }|r't#          t$                              |                    S t          |                              ddh          rg S g S |                                d}t)          fd|D                       rt#          t*                    S g S )zGReturn supported reasoning-effort levels for *model_id*, or [] if none.r
   Nr  r  r  r   )github_model_reasoning_efforts lmstudio_model_reasoning_optionsr  r)  r  r    r  r=  c                     g | ]V}t          |                                          #t          |                                                                          WS rc   )r4   r   r   rk  opts     r   rm  z3resolve_model_reasoning_efforts.<locals>.<listcomp>|  sI    SSS3#c((..BRBRSc#hhnn&&,,..SSSr   offc                 $    g | ]}|t           v |S rc   )r  r  s     r   rm  z3resolve_model_reasoning_efforts.<locals>.<listcomp>  s#    RRRc3:Q3Q3Qc3Q3Q3Qr   onr  c              3   B   K   | ]}                     |          V  d S r  r  )rk  rR  model_lowers     r   rv  z2resolve_model_reasoning_efforts.<locals>.<genexpr>  s1      
A
Af;!!&))
A
A
A
A
A
Ar   )r4   r   r   rY  r\   ra   r}   r^  r\  r  r  r  r  r/  r;  r  issubsetrn  r|   fromkeysr  r  )r<  r  r~  r  r  resolved_base_urlr  r  r  hinted_modelrS  
probe_baseoptsr   
level_optsr  r  s                   @r   resolve_model_reasoning_effortsr  S  s    B%%''E 	9DLs;$"%%++--33555"HHN++1133;t [	[-CE-J-J*Ax** 	[ 	[ 	[CGGG,,277
CCIrJJPPRRXXZZHHH	[ 'x00H000	=	
 	
 	
 	
 	
 	
 	
 	
 	
  = = =+E8<<<<<= 6e<<L000--l;;;>!!""3**2.--d333:&J*@*J*J
//zBBSS$SSS
 	S__55ug>> 	IRRZRRR
 	3j11222z??##UDM22 	I	++--K
H 
A
A
A
A
A
A
AAA -+,,,Is%   
B A,DD&D/ /EE)r<  r  r~  c                 >   t          t                                }|                    d          pi }|                    d          pi }t          |t                    r|                    d          nd}t          |t                    r|                    d          nd}t          | ||          }t          |t                    rt          |          ndt          |pd                                          	                                |t          |          d	S )
u2  Return current reasoning configuration from the active profile's
    config.yaml — the same source of truth the CLI reads from.

    Keys:
      - show_reasoning: bool — from ``display.show_reasoning`` (default True)
      - reasoning_effort: str — from ``agent.reasoning_effort`` ('' = default)
    displayagentshow_reasoningNreasoning_effort)r  r~  Tr
   )r  r  supported_effortssupports_reasoning_effort)
r   rl   r}   r{   r|   r  boolr4   r   r   )	r<  r  r~  rx   display_cfg	agent_cfgshow_raw
effort_rawr  s	            r   get_reasoning_statusr    s    ))9););<<K//),,2K((.BI4>{D4Q4Q[{/000W[H6@D6Q6Q[1222W[J7   -7x,F,FP$x...D
 0b117799??AA.%)*;%<%<  r   showc                 X   t                      }t          5  t          |          }|                    d          }t	          |t
                    si }t          |           |d<   ||d<   t          ||           ddd           n# 1 swxY w Y   t                       t                      S )a  Persist ``display.show_reasoning`` to the active profile's config.yaml.

    Mirrors CLI ``/reasoning show|hide``: writes the same key that the CLI
    writes, so the preference is shared across the WebUI and the terminal
    REPL for the same profile.
    r  r  N)
rl   r   r   r}   r{   r|   r  r   rs   r  )r  rt   rx   r  s       r   set_reasoning_displayr    s     #$$K	 9 9,[99!ooi00+t,, 	K(,T

$%!,I{K8889 9 9 9 9 9 9 9 9 9 9 9 9 9 9 OOO!!!s   A#BB	B	r  c                 D   t          | pd                                                                          }|st          d          |dk    r7|t          vr.t          d|  dd                    t                     d          t                      }t          5  t          |          }|	                    d          }t          |t                    si }||d	<   ||d<   t          ||           d
d
d
           n# 1 swxY w Y   t                       t                      S )a+  Persist ``agent.reasoning_effort`` to the active profile's config.yaml.

    Mirrors CLI ``/reasoning <level>``: same key, same valid values
    (``none`` | ``minimal`` | ``low`` | ``medium`` | ``high`` | ``xhigh``).
    Raises ``ValueError`` on an unrecognised level so callers can return 400.
    r
   zeffort is requiredr  zUnknown reasoning effort 'z'. Valid: none, z, r  r  r  N)r4   r   r   r'  r  r   rl   r   r   r}   r{   r|   r   rs   r  )r  r(   rt   rx   r  s        r   set_reasoning_effortr    sy    fl


!
!
#
#
)
)
+
+C /-...
f}}$;;;B B B II&=>>B B B
 
 	
 #$$K	 9 9,[99OOG,,	)T** 	I(+	$%(G{K8889 9 9 9 9 9 9 9 9 9 9 9 9 9 9 OOO!!!s   AC;;C?C?c                    t          | pd                                          }|st          d          t                      }t          5  t          |          }|                    di           }t          |t                    si }t          |                    d          pd                                          }t          |          \  }}}t          |p|                                          }	t          |p|pd                                          }
|

                                dk    rd}
|	|d<   |
r|
|d<   |r8t          |                                                              d          |d	<   n=|
|k    r7|
d
k    rd|d	<   n+|
                    d          s|                    d	d           ||d<   t          ||           ddd           n# 1 swxY w Y   t                       t!                       d|	dS )zJPersist the Hermes default model in config.yaml and reload runtime config.r
   zmodel is requiredr  r  rY  rW  rY   r  r~  r(  zhttps://api.openai.com/v1r`  NT)r   r  )r4   r   r'  rl   r   r   r}   r{   r|   rY  r   r  rb  r  r   rs   invalidate_models_cache)r<  selected_modelrt   rx   r8  previous_providerresolved_modelresolved_providerr  persisted_modelpersisted_providers              r   set_hermes_default_modelr    sg   R((..00N .,---"$$K 
 *9 *9,[99OOGR00	)T** 	I	j 9 9 ?R@@FFHH?U@
 @
<)+< n>??EEGG !2!M6G!M2NNTTVV ##%%00!).	) 	7$6Ij! 	0$'(9$:$:$@$@$B$B$I$I#$N$NIj!!#444!X--(C	*%%'229== 0j$///(G{K888U*9 *9 *9 *9 *9 *9 *9 *9 *9 *9 *9 *9 *9 *9 *9X OOO
 111s   
F GGG)	visionweb_extractcompressionr  
skills_hubapprovalmcptitle_generationcurator.AUX_TASK_SLOTSc                     t                       t                              di           } t          | t                    si } t          |                     d          pd                                          }t          |                     d          p|                     d          pd                                          }t                              di           }t          |t                    si }g }t          D ]}|                    |i           }t          |t                    si }|                    |t          |                    d          pd                                          t          |                    d          pd                                          t          |                    d          pd                                          d	           |||d
dS )a)  Return current auxiliary task assignments from config.yaml.

    Shape:
    {
        "tasks": [
            {"task": "vision", "provider": "auto", "model": "", "base_url": ""},
            ...
        ],
        "main": {"provider": "openrouter", "model": "anthropic/claude-opus-4.7"},
    }
    r  r  r
   rY   r   	auxiliaryautor~  )taskr  r  r~  )r  r  )tasksmain)	rs   ra   r}   r{   r|   r4   r   r  r1   )r8  main_provider
main_modelaux_cfgr  slotrl  s          r   get_auxiliary_modelsr  8	  s    OOO$$Ii&& 		j117R88>>@@MY]]9--Lv1F1FL"MMSSUUJggk2&&Ggt$$ E 	 	D"%%%&& 	EEIIj11;V<<BBDD7++1r2288::EIIj117R88>>@@	
 
 	 	 	 	 *Z@@  r   r  r  c                    | dk    r0| t           vr't          d| dt          t                                t                      }t          5  t          |          }| dk    ry|                    di           }t          |t                    si }t           D ]>}|                    |i           }t          |t                    si }d|d<   d|d<   |||<   ?||d<   n|                    di           }t          |t                    si }|                    | i           }t          |t                    si }|pd|d<   |pd|d<   |ry|	                    d	          s|d
k    r^	 t          |          \  }}}	|	r7t          |	                                                              d          |d<   n# t          $ r Y nw xY w||| <   ||d<   t          ||           ddd           n# 1 swxY w Y   t!                       d| ||dS )zzPersist an auxiliary model assignment in config.yaml.

    Special case: task='__reset__' clears all auxiliary slots.
    	__reset__zUnknown auxiliary task slot: z	. Valid: r  r  r  r
   r  r`  rW  r  r~  NT)r   r  r  r  )r  r'  rn  rl   r   r   r}   r{   r|   rb  rY  r4   r   r  r\   r   rs   )
r  r  r  rt   rx   r  r  slot_cfgr  r  s
             r   set_auxiliary_modelr  a	  s   
 {t>99SDSST.=Q=QSS
 
 	
 #$$K	 $9 $9,[99; "ook266Ggt,, & ) )";;tR00!(D11 "!H'-$$&! ('.K$$!ook266Ggt,, {{4,,Hh-- #+#5vHZ  %HW X00;; x8?S?S.DU.K.K+Aq+( Z/23D/E/E/K/K/M/M/T/TUX/Y/Y,    D$GDM'.K${K888I$9 $9 $9 $9 $9 $9 $9 $9 $9 $9 $9 $9 $9 $9 $9L OOO(UKKKs8   DG$!AF.-G$.
F;8G$:F;;G$$G(+G(_available_models_cache_available_models_cache_ts*_available_models_cache_source_fingerprint     @_AVAILABLE_MODELS_CACHE_TTLCredentialPool_CREDENTIAL_POOL_CACHE_provider_models_invalidated_tsc                      	 ddl } | j                            d          }|dS t          |dd          }|rt	          |          ndS # t
          $ r Y dS w xY w)u  Lazy resolver for the WebUI version, used to stamp the disk cache (#1633).

    `api.updates` imports `api.config` at module-load time, so we cannot
    `from api.updates import WEBUI_VERSION` at the top of this module without a
    circular import. Instead we resolve lazily on each cache load/save.

    Returns the runtime version string (e.g. ``v0.50.293``) when api.updates
    has been imported, or None if it isn't loaded yet (boot-time corner case
    before the server has finished initializing). A None return is treated as
    "do not stamp / do not validate" by the cache layer so cache reads/writes
    that happen during early init still work — the next call after init will
    stamp normally.
    r   Nzapi.updatesWEBUI_VERSION)sysmodulesr}   getattrr4   r\   )_sysr   r  s      r   _current_webui_versionr  	  sz    	l}--;4C$//$s1vvv$   tts    A #A 
AAr  models_cache.jsonc                  ^    	 ddl m}   |             dz  S # t          $ r t          dz  cY S w xY w)z8Return the auth.json path for the active Hermes profile.r   rg   z	auth.json)ri   rh   rj   r5   )_gahs    r   _get_auth_store_pathr  	  sV    2??????tvv## 2 2 2#k11112s    ,,c                    dt          t          |                                                     i}	 t          |                                           }n# t          $ r
 d|d<   |cY S w xY w|j        |d<   |j        |d<   |S )a0  Return non-secret identity metadata for a cache dependency file.

    The /api/models response depends on config.yaml (model/provider defaults)
    and auth.json (active_provider + credential_pool).  The cache only needs
    cheap invalidation signals here, not file contents; never include secrets.
    r=   Tr   mtime_nssize)r4   r   r2   rp   rr   st_mtime_nsst_size)r=   fingerprintsts      r   _models_cache_file_fingerprintr  	  s     3tDzz4466778K$ZZ__   !%I !nK
*Ks   !A A('A(c                     t           t          d} 	 t          j        | dddt                                        d          }t          j        |                                          }n# t          $ r d}Y nw xY wt          t          j        dd                                          p	t          d	z                                            }|t!          |d
z            dS )a  Return non-secret model-catalog identity metadata for cache invalidation.

    The /api/models payload is not only a function of user config/auth files.
    It also depends on the provider/model catalog baked into this module and on
    small local catalogs such as Codex's models_cache.json. Keep this cheap and
    deterministic so a server restart after catalog changes does not keep
    serving an otherwise-valid persisted models_cache.json until the 24h TTL
    expires (#2443).
    )provider_modelsprovider_displayTrT   rW   rX   ensure_asciirY   r   unavailable
CODEX_HOMEr
   .codexr  )provider_catalog_sha256codex_models_cache)r  r  rZ   r[   r4   encodehashlibsha256	hexdigestr\   r   r   r   r   r   r2   r  )catalog_payloadencodedprovider_catalog_sha
codex_homes       r   !_models_cache_catalog_fingerprintr  	  s     ,- O
-*!
 
 
 &// 	  '~g66@@BB - - -,- bib117799NdXoOOZZ\\J#7<ZJ]=]^^  s   AA) )A87A8>   secretr  id_token
expires_at
expires_in
updated_atlast_statusaccess_tokenclient_secretexpires_at_msrefresh_tokenrequest_countlast_status_atlast_error_codelast_error_reasonlast_error_messagelast_error_reset_atc                     t          | t                    rd |                                 D             S t          | t                    rd | D             S | S )a  Recursively drop credential-rotation-only keys from an auth.json tree.

    Pure structural transform; never mutates the input. Any key NOT in the
    deny-list is preserved verbatim so real provider/endpoint changes still
    show through in the fingerprint.
    c                 F    i | ]\  }}|t           v|t          |          S rc   )_AUTH_FINGERPRINT_VOLATILE_KEYS_strip_volatile_auth_fieldsrk  kr  s      r   
<dictcomp>z/_strip_volatile_auth_fields.<locals>.<dictcomp>H
  s=     
 
 
1777 *1--777r   c                 ,    g | ]}t          |          S rc   )r  )rk  r  s     r   rm  z/_strip_volatile_auth_fields.<locals>.<listcomp>N
  s!    <<<1+A..<<<r   )r{   r|   r  rn  )objs    r   r  r  @
  sl     #t 

 
		
 
 
 	

 #t =<<<<<<Jr   c                    t          |                                           }dt          |          i}	 |                                }n# t          $ r
 d|d<   |cY S w xY w	 t          j        |                    d                    }n+# t          $ r |j	        |d<   |j
        |d<   d|d	<   |cY S w xY wt          |          }	 t          j        |dd
dt                                        d          }t          j        |                                          |d<   n)# t          $ r |j	        |d<   |j
        |d<   d|d	<   Y nw xY w|S )u  Return a content fingerprint of auth.json that ignores token churn.

    Unlike _models_cache_file_fingerprint() (mtime_ns + size), this hashes
    the JSON content with the credential-rotation fields stripped, so the
    ~14-min token-refresh rewrite of auth.json does NOT invalidate the 24h
    /api/models cache. A change to anything that actually affects the
    provider/model set (active_provider, a new credential_pool entry, a
    changed base_url/source/label/auth_type, the providers{} block, …)
    still changes the hash and correctly busts the cache.

    Failure modes are deliberately conservative — if the file is missing we
    record that, and if it can't be read/parsed we fall back to the old
    mtime/size fingerprint so behaviour is never *less* safe than #1699.
    r=   Tr   r   r   r  r  zunparsed-fallbacksemanticrT   r  semantic_sha256zencode-fallback)r   r2   r4   rp   rr   rZ   loadsr   r\   r  r  r  r[   r  r   r  r  )r=   pfpr  r(   strippedr  s          r    _auth_store_semantic_fingerprintr(  R
  s    	T

AABVVXX   9				jg6677    :Z6
,:			 +3//H+*!
 
 
 &// 	 !(w 7 7 A A C C + + +:Z6
*:+ Is6   A	 	AA!(B
 
%B21B2AD   #EEc                      t          t                                t          t                                t	                      dS )aP  Return the current config/auth/catalog fingerprint for /api/models cache.

    The auth.json axis uses a *content* fingerprint that excludes pure
    credential-rotation fields (see _auth_store_semantic_fingerprint): the
    auth store is rewritten roughly every 14 minutes by token refresh, and
    a stat-based (mtime/size) fingerprint made the 24h cache churn on every
    one of those rewrites (RCA t_16551f61). config.yaml keeps the cheap
    mtime/size fingerprint because it is only rewritten on deliberate user
    edits (which can change anything) and does not churn on a timer.
    )config_yaml	auth_jsoncatalog)r  rl   r(  r  r  rc   r   r    _models_cache_source_fingerprintr-  
  s@     66F6H6HII56J6L6LMM466  r   c                  v    	 t          j        t          t                               d S # t          $ r Y d S w xY wr  )r   unlinkr4   _models_cache_pathrr   rc   r   r   r   r   
  sH    
	#())*****   s   &* 
88cachec                    t          | t                    sdS h d                    |           sdS |                     d          }|du st          |t                    owt          |                     d          t                    oOt          |                     d          t                    o't          |                     d          t
                    S )a  Return True when a cache payload has the full /api/models shape.

    SHAPE-only check: validates structural correctness of an in-memory or
    on-disk cache. Use _is_loadable_disk_cache() for the strictness needed
    when reading from disk (it adds version-stamp invalidation per #1633).

    Kept loose so in-memory cache writes (which never touch disk and so don't
    need version stamping) can use this validator unchanged.
    F>   r  rv  r  configured_model_badgesr  Nrv  r3  r  )r{   r|   r  r}   r4   rn  )r1  r  s     r   _is_valid_models_cacher4  
  s     eT"" uTTT]]^cdd uii 122O	D	 	DJ$D$D 	2uyy11377	2uyy!:;;TBB	2 uyy**D11	r   c                 &   t          |           sdS t          | t                    sdS |                     d          }|t          k    r#t
                              d|t                     dS t                      }|N|                     d          }t          |t                    r||k    rt
                              d||           dS |                     d          }t                      }||k    rt
                              d||           dS d	S )
u  Return True when an on-disk cache is safe to use after a process boot.

    Adds two checks on top of _is_valid_models_cache (#1633):
      1. ``_schema_version`` matches `_MODELS_CACHE_SCHEMA_VERSION`. A bumped
         schema version unconditionally invalidates older cache files.
      2. ``_webui_version`` matches the current runtime version. Forces a
         rebuild after every release so users see picker-shape fixes
         immediately, instead of waiting up to 24 hours for the TTL to expire.
         If the runtime version cannot be resolved (early-init edge case),
         skip this check rather than wedge the boot.

    Note: ``_webui_version`` is a string equality check, not a semver compare —
    two debug builds with the same `WEBUI_VERSION` string but different actual
    code wouldn't invalidate via this axis. ``_schema_version`` is the
    independent invalidation axis for breaking changes that lack a tag bump;
    bump it whenever the cache shape changes incompatibly.
    F_schema_versionz.models cache rejected: schema=%r vs runtime=%rN_webui_versionz5models cache rejected: webui_version=%r vs runtime=%r_source_fingerprintz:models cache rejected: source_fingerprint=%r vs runtime=%rT)
r4  r{   r|   r}   _MODELS_CACHE_SCHEMA_VERSIONr$   r   r  r4   r-  )r1  cached_schemaruntime_versioncached_versioncached_sourcesruntime_sourcess         r   _is_loadable_disk_cacher?  
  s-   $ "%(( ueT"" uII/00M444 	<7	
 	
 	
 u,..O"#344.#.. 	.O2S2SLLG   5YY455N688O((H	
 	
 	

 u4r   c                  J   	 ddl } t                                          sdS t          t          d          5 }|                     |          }ddd           n# 1 swxY w Y   t          |          sdS |d         |d         |d         |d         d	S # t          $ r Y dS w xY w)
a  Load /api/models cache from disk if it exists and has current metadata.

    Adds the per-release version check from #1633: a cache stamped with a
    different WebUI version is treated as missing, forcing a fresh rebuild
    that picks up any picker-shape fixes shipped in the new release. The
    returned dict is the SHAPE-only cache (without the `_webui_version` /
    `_schema_version` stamps) so callers don't have to know about the
    on-disk metadata fields.
    r   Nr   r   r  rv  r3  r  )r  rv  r3  r  )rZ   r0  r8   openloadr?  r\   )_jfr1  s      r   _load_models_cache_from_diskrE  
  s   !((** 	4$w777 	1GGAJJE	 	 	 	 	 	 	 	 	 	 	 	 	 	 	&u-- 	4  %%67"?3',-F'GHo	
 
 	
    tts?   B B AB AB  A!B 5B 
B"!B"c                    	 t          |           sdS t          t                      | d         | d         | d         | d         d}t                      }|||d<   t	          t
                    dt          j                     d	z   }t          |d
d          5 }t          j
        ||d           ddd           n# 1 swxY w Y   t          j        |t	          t
                               dS # t          $ r Y dS w xY w)u3  Save cache to disk so it survives server restarts.

    Stamps the payload with `_webui_version` and `_schema_version` (#1633) so
    a subsequent process running a different WebUI version, or a future
    release that bumps the schema, will treat the file as invalid and
    rebuild from live provider data on its first /api/models call.

    The version stamp is omitted (not the literal None — the field is just
    skipped) when the runtime version cannot be resolved at the moment of
    save, which would happen only in a very early boot path before
    api.updates is loaded. _is_loadable_disk_cache treats a missing field as
    a mismatch (since runtime_version is non-None on every subsequent call),
    so this is safe — at worst we write one cache file that gets rejected
    once on the next boot.
    Nr  rv  r3  r  )r6  r8  r  rv  r3  r  r7  r  z.tmpwr   r   r  )indent)r4  r9  r-  r  r4   r0  r   getpidrA  rZ   dumprenamer\   )r1  payloadr;  tmprD  s        r   _save_models_cache_to_diskrN    sY    %e,, 	F;#C#E#E$%67"?3',-F'GHo
 
 122&(7G$%$%%(=BIKK(=(=(==#sW--- 	,Igq++++	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	,
	#s-../////   s;   C0 BC0 B<0C0 <C  C0 C *C0 0
C>=C>nowc                 ,   t           dS | t          z
  t          k    rdS t                      }t          |k    r)t
                              dt          |           da dadadS t          t                     rt          j	        t                     S da dadadS )zHReturn a valid fresh in-memory /api/models cache, or clear stale shapes.NzAmodels memory cache rejected: source_fingerprint=%r vs runtime=%rrN   )
r  r  r  r-  r  r$   r   r4  copydeepcopy)rO  current_sourcess     r   _get_fresh_memory_models_cacherT  ,  s     &t((-HHHt688O1_DDO6	
 	
 	

 #'%("592t566 6}4555"!$15.4r   c                      t           5  dadadadat
                                           t                                           ddd           n# 1 swxY w Y   t                       dS )a  Force the TTL cache for get_available_models() to be cleared.

    Call this after modifying config.cfg in-memory (e.g. in tests) so
    the next call to get_available_models() picks up the changes rather
    than returning a stale cached result.

    Also deletes the on-disk cache so that a subsequent cold build does
    not immediately reload a stale disk snapshot and skip the fresh build.
    This is essential for test isolation: without the disk delete, tests
    that call invalidate_models_cache() still get back the previous test's
    result from the disk cache because the disk hit is checked before the memory
    cache rebuild runs.
    NrN   F)
_available_models_cache_lockr  r  r  _cache_build_in_progress_cache_build_cv
notify_allr  r   r   rc   r   r   r  r  F  s     
& 
' 
'"&%("592#( ""$$$
 	$$&&&
' 
' 
' 
' 
' 
' 
' 
' 
' 
' 
' 
' 
' 
' 
' !"""""s   ;AAAc                 x   t           5  t                              | d           t                              t          |           d           ddd           n# 1 swxY w Y   	 ddlm}  ||             |t          |                      dS # t          $ r  t                              dd           Y dS w xY w)zInvalidate the credential pool cache for a specific provider.

    Used by the streaming layer's credential self-heal logic (#1401) to
    force a fresh credential pool load after re-reading auth.json.
    Nr   )%invalidate_account_usage_status_cachez/Failed to invalidate account usage status cacheTexc_info)	rV  r  r  r^  api.providersr[  r\   r$   r   )r  r[  s     r    invalidate_credential_pool_cacher_  e  s1    
& O O"";555""#:;#G#GNNNO O O O O O O O O O O O O O OW 	HGGGGG--k:::--.Ek.R.RSSSSS W W WFQUVVVVVVWs$   AAAA$)B &B98B9c                 (   t           5  dadadat	          j                    t
          | <   t                              | d           t                              t          |           d           ddd           n# 1 swxY w Y   t                       dS )a  Invalidate cached models for a single provider.

    Also invalidates the full cache so that the next get_available_models()
    call rebuilds all groups cleanly (the rebuilt provider is merged with any
    other cached groups from the 24h TTL window).  After the next
    get_available_models() call, _provider_models_invalidated_ts[provider_id]
    is cleared so the provider's fresh models are used.

    Args:
        provider_id: canonical provider id (e.g. 'openai', 'anthropic', 'custom:my-key')
    NrN   )
rV  r  r  r  timer  r  r  r^  r   )r  s    r    invalidate_provider_models_cacherb  z  s     
& 	O 	O"&%("5927;y{{'4 	"";555""#:;#G#GNNN	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O !"""""s   A%A99A= A=existing_groupsc           
      8   | }|                     d          r d|v r|                    dd          d         }d } ||          }|D ]l}|                    dg           D ]S}|                    d          r< |t          |                    dd                              |k    r|d         c c S Tmd	|v r|                    d	          d
         n|}d                    d |                    dd                              d          D                       S )a  Return a human-friendly label for *model_id*.

    Resolution order:
    1. If the model already appears in *existing_groups* with a label, use it.
    2. Strip @provider: prefix and namespace prefix, then title-case.

    This ensures the injected default model entry in the dropdown always shows
    the same label as the live-fetched or static-catalog version, rather than
    the raw lowercase ID string (#909).
    r  rV   r    c                     d| v r|                      dd          d         n|                     dd                                          S )Nr  r    r  ra  r  )r  r  r   )r  s    r   r  z&_get_label_for_model.<locals>.<lambda>  s@    cQhhqwwsAr**AFFsCPPVVXX r   r  r  r  r
   r  r  r  c              3     K   | ]{}t          |          d k    rP|                    dd                                          r(|                                s|                                n|                                V  |dS )r  r  r
   N)r   r  r  r  r  
capitalize)rk  rG  s     r   rv  z'_get_label_for_model.<locals>.<genexpr>  s         !ffkkaiiR&8&8&@&@&B&Bk199;;k			]^]i]i]k]k     r   r  ra  )rb  r  r}   r4   r   r  )r<  rc  	lookup_id_normnorm_lookupgr)   rS  s           r   _get_label_for_modelrl    sR    IC   /SI%5%5OOC++A.	 YXE%	""K " "x$$ 	" 	"AuuW~~ "%%AEE$OO(<(<"="="L"Lz!!!!!	" (+i'7'79??3##YD88  c3''--c22     r   c                 d   t          | pd                                          }|sg S 	 ddlm} n# t          $ r g cY S w xY w|g}	 t          |          }n# t          $ r d}Y nw xY w|r||vr|                    |           t                      }|D ]}	  ||          pg }n+# t          $ r t          	                    d|           Y 8w xY wg }|D ]U}	t          |	pd                                          }
|
r.|
|vr*|
                    |
           |                    |
           V|r|c S g S )a  Return live model IDs from Hermes CLI for a provider, or [] on failure.

    WebUI's static ``_PROVIDER_MODELS`` table is only a fallback.  The agent CLI
    owns the provider registry and catalog-discovery logic, so ordinary picker
    groups should ask ``hermes_cli.models.provider_model_ids()`` first (#1240).
    Provider aliases are tried as a secondary lookup because WebUI keeps a few
    display-facing IDs (for example ``google`` / ``x-ai``) that Hermes CLI may
    normalize internally.
    r
   r   provider_model_idsz(Failed to load %s models from hermes_cli)r4   r   r\  ro  r\   r^  r1   r  r$   r   r   )r  r  _provider_model_idsr9   aliasr  r   r  r  r  mid_ss              r   _read_live_provider_model_idsrs    s    kR
 
 
&
&
(
(C 	OOOOOOO   			 J',,    !j((%   UUD  		**955;HH 	 	 	LLCYOOOH	  	% 	%C	rNN((**E %d**e$$$ 	MMM	Is0   0 ??A A%$A%B$$%CCc                 2   | dv rt           nd}g }t                      }|D ]v}t          |pd                                          }|r||v r,|                    |           |r ||          nt          |g           }|                    ||d           w|S )z=Convert Hermes CLI model ids into WebUI picker model entries.r9  r:  Nr
   r  )r  r  r4   r   r   rl  r1   )r  r  	formatterr  r  r  rr  r  s           r   _models_from_live_provider_idsrw    s    (37Q(Q(Q$$W[IFUUD 5 5CI2$$&& 	$-R		%   3Gr3R3RUU334444Mr   c                  \   t          t          j        dd                                          p	t          dz                                            } | dz  }	 t          j        |                    d                    }n# t          $ r g cY S w xY wt          |t                    r|                    d          nd}t          |t                    sg S g }|D ]}t          |t                    s|                    d	          }t          |t                    r|                                sX|                    d
d          }t          |t                    r)|                                                                dv r|                    d          }t          |t           t"          f          rt!          |          nd}	|                    |	|                                f           |                    d            g }
|D ]\  }}||
vr|
                    |           |
S )az  Return visible model slugs from Codex's local models_cache.json.

    The agent's provider_model_ids('openai-codex') intentionally filters IDs
    with ``supported_in_api: false``. Codex CLI still lists some of those models
    in its picker (notably ``gpt-5.3-codex-spark`` from #1680), so the WebUI
    merges this visible local catalog to stay in sync with Codex itself.
    r  r
   r  r  r   r   r  Nrd  
visibility)hidehiddenr  i'  c                 "    | d         | d         fS )Nr   r    rc   )items    r   r  z5_read_visible_codex_cache_model_ids.<locals>.<lambda>  s    DGT!W#5 r   r  )r   r   r   r   r   r2   rZ   r$  r   r\   r{   r|   r}   rn  r4   r   r&   floatr1   sort)r  
cache_pathrL  rp  sortabler}  rd  ry  r  rankorderedr  s               r   #_read_visible_codex_cache_model_idsr    s#    bib117799NdXoOOZZ\\J11J*Z1171CCDD   			 (2'4'@'@Jgkk(###dGgt$$ 	&(H . .$%% 	xx$$$ 	DJJLL 	XXlB//
j#&& 	:+;+;+=+=+C+C+E+EI[+[+[88J'' *8c5\ B BNs8}}}tzz||,----MM55M666G ! !4wNN4   Ns   (B   BBc                     	 t                      } |                                 j        }n # t          $ r t                      } d}Y nw xY w|t          k    s| t
          k    rt                      st                       dt          fd}t          }	 t          t                                                                j        }n# t          $ r d}Y nw xY w|t          k    }d}t          t                      }t          5  |rMt                              d d           t!          t#          j                              }||cddd           S |rt                       dadadad}t#          j                    }t!          |          }||cddd           S |A|a|at+                      at-          |           t/          j        |          cddd           S t          5  da	ddd           n# 1 swxY w Y   	  |            }nI# t2          $ r< t          5  d	a	t                                           ddd           n# 1 swxY w Y    w xY wt          5  |at#          j                    at+                      ad	a	t                                           ddd           n# 1 swxY w Y   t-          |           t/          j        |          cddd           S # 1 swxY w Y   dS )
a  
    Return available models grouped by provider.

    Discovery order:
      1. Read config.yaml 'model' section for active provider info
      2. Check for known API keys in env or ~/.hermes/.env
      3. Fetch models from custom endpoint if base_url is configured
      4. Fall back to hardcoded model list (OpenRouter-style)

    Returns: {
        'active_provider': str|None,
        'default_model': str,
        'groups': [{'provider': str, 'models': [{'id': str, 'label': str}]}]
    }
    rN   r   c                  J   d t          t                    g dt          dt          fddt          t          t          t          t          f         f         ffd} d}t                              di           d}t          t                    rnZt          t                    rE                    d                              dd          }                    d	d          }|r|rt          t          |
          i }t                      }|                                r	 dd l	}|
                    |                    d                    }s*t          |                    d          t          |
          n+# t          $ r t                              d|           Y nw xY wt                      }r|                               	 t          |t                    r|                    di           ni }t          |t                    r)|r&	 ddlm} t'          |                                          D ]^}		 t+          t          |	                    }
t,                              |	          }||\  }}t/          j                    |z
  dk     r|                                }nt/          j                    } ||	          }t/          j                    |ft,          |	<   |                                }nOt/          j                    } ||	          }t/          j                    |ft,          |	<   |                                }d |D             }|r|                    |
           4# t          $ r t                              d|	           Y \w xY wn# t4          $ r |                                D ]x\  }	}t          |t&                    rt9          |          dk    r.t;          d |D                       }|r/|                    t+          t          |	                               yY nw xY wn*# t          $ r t                              d           Y nw xY wi }d}	 ddlm} ddl m!}  |            D ]}|                    d          s	  ||d                                       dd          }|dk    rEn?# t          $ r2 t                              d|                    dd                     Y nw xY w|                    |d                    d }	  |d!                              d"          r|                    d!           n*# t          $ r t                              d#           Y nw xY wn*# t          $ r t                              d$           Y nw xY w|s`	 dd%l"m#}  |            d&z  }n# t4          $ r tH          d&z  }Y nw xY wi }|                                r	 |                    d          %                                D ]}|&                                }|r|'                    d'          sld(|v rh|(                    d(d)          \  }}|&                                &                    d*          &                    d+          ||&                                <   n*# t          $ r t                              d,           Y nw xY wi |}d-D ]}tS          j*        |          }|r|||<   |                    d.          r|                    d/           |                    d0          r*|                    d1           |                    d2           |                    d3          r|                    d4           |                    d5          r|                    d6           |                    d7          r|                    d8           |                    d9          r|                    d:           |                    d;          r|                    d<           |                    d=          r|                    d>           |                    d?          r|                    d@           |                    dA          r|                    dB           |                    dC          r|                    dD           |                    dE          r|                    dF           |                    dG          r|                    dH           |                    dI          r|                    dJ           |                    dK          r|                    dL           |                    dM          r*|                    dN          r|                    dO           |                    dP          r*|                    dQ          r|                    dR           t                              dSi           } i }!t          | t                    r|                                 D ]n\  }"}#tW          |"          }$|$s|$tX          v p|$tZ          v }%t          |#t                    }&|%s|&sC|!.                    |$|"           |                    |$           od	t^          dt          ffdT}'d	t          dt          fdUdVt^          dt          dt&          t                   fdWd dXdt          dYt          dZt`          d z  dt          fd[dd\d]d	t^          dt          d^t^          d_tb          t^          d`f         dtb          t&          t                   t          d z  f         f
fda}(g })i }*|r|&                                }+ |'|+          },|,pdb}-te          |,          }.tg          dc|+v r|+ndd|+           }/|/j4        p|/j5        6                                }0|/j7        rc|.sa	 dd l8}1|19                    |/j7                  }2|2j:        s|2j;        s|2j<        rde|0v sdf|0v sdg|0v rde}-ndR|0v sdh|0v rdR}-ndb}-n# tz          $ r Y nw xY wd}3t          t                    r)                    d^          pd&                                }3|3st                              dSi           }4t          |4t                    rmt}          d dbg          D ]Z}5|4                    |5i           }6t          |6t                    r-|6                    d^          pd&                                }3|3r n[|3sHdi}7|7D ]C}8|                    |8          ptS          j*        |8          pd&                                }3|3r nD|g}9t                              djg           }:t          |:t&                    r|9?                    dk |:D                         |(|+|-|3tc          |9          ]          \  };}<|;D ]i}=|)@                    |=           |-6                                }5|*.                    |5g           @                    |=           |                    |5           jt                              djg           }>i }?i }@t          |>t&                    r3t                      }A|>D ]!}Bt          |Bt                    s|B                    dl          pd&                                }C|Crt          |C          nd }D|Dr|D|?vr|Cg f|?|D<   t          |B                    d	          pd          &                                }E|Dr|Ert          |B                    d^          pd          &                                }F|Fsmt          |B                    dm          pd          &                                }G|Gr5t          tS          j*        |G          pd          &                                }F|B                    dn          }Ht          |Ht          t&          f          ot9          |H          dk    }I|*                    |D          }Jd }K|Ir|Jg }Jn|J |(|E|D|F|Ef]          \  }J}K|Kr|K|@|D<   |                    |D           |JD ]}Lt          |L                    d          pd          &                                }M|Ms;|D do|M }N|N|Av rG|A                    |N           |                    |D           |M}O|Dk    r|O'                    dp          sdp|D do|O }O|?|D         d)         @                    |O|L                    dq          pt          |Mg           dr           g }P|B                    dd          }Q|Qr|P@                    |Q           |B                    dn          }Rt          |Rt                    rZ|RD ]V}St          |St                    r?|S&                                r+|S|Pvr'|P@                    |S&                                           Wnt          |Rt&                    r|RD ]}Tt          |Tt                    r0|T&                                }U|Ur|U|Pvr|P@                    |U           Gt          |Tt                    r{t          |T                    d          p+|T                    d          p|T                    dl          pd          &                                }U|Ur|U|Pvr|P@                    |U           |PD ]}Q|Dr|D do|Q n|Q}N|Qr|N|Avrt          |Qg           }V|A                    |N           |Dr_|                    |D           |Q}O|Dk    r|O'                    dp          sdp|D do|O }O|?|D         d)         @                    |O|Vdr           |)@                    |Q|Vdr           |                    db           ǐ#t          |>t&                    ot9          |>          dk    }Wr\dbk    rV|WsT|C                    db           t'          |          D ].}D|D'                    ds          r|Ws|C                    |D           /n8dbk    r2|Wr0t;          dt |>D                       }X|Xs|C                    db           t          t                    }Yt          |t                    }Z|Zrw|Yrut'          |          D ]e}	t          |	pd          &                                6                                }[|['                    ds          r|[|Yvr|C                    |	           ft                              dSi           }4t          |4t                    r|4                    dud          nd}\|\rt                      }]r|]                               t                              dSi           }^t          |^t                    r1|]F                    dv |^                                D                        |G                    |]          }|r;t                      }_|D ](}	tW          |	          p|	}`|_                    |`           )|_}|rKt          |          D ]9}a|a'                    ds          rX|a|?v rS|?|a         \  }b}c|cs|*                    |ag           }c|cs|a|@v r*|b|a|cdw}d|a|@v r|@|a         |ddx<   @                    |d           ptZ                              |a|aI                                          }e|ad4k    r)g }ft                      }g	 ddylmJ}h  |h            pg }i|iD ]8\  }j}k|jr1|j|gvr-|g                    |j           |f@                    |j|jdr           9n*# t          $ r t          K                    dz           Y nw xY w	 dd lLmM}l |lN                    d{d|d}i~          }m|lO                    |md          5 }nt          j
        |nP                                Q                                          }od d d            n# 1 swxY w Y   d}pd}q|o                    dVg           pg D ]}Tt          |Tt                    st          |T                    d          pd          &                                }U|Ur|U|gv rV|T                    d          pi }r	 t          |r                    dd          pd          dk    o(t          |r                    dd          pd          dk    }sn# t          tz          f$ r d}sY nw xY w|sp|UT                    d          }s|sst          |T                    dl          pd          &                                p|U}td|tv r|t(                    d          d         n|t}ud|u6                                vr|u d}u|g                    |U           |f@                    |U|udr           |pd)z  }p|p|qk    r nn*# t          $ r t                              d           Y nw xY w|fsd t          D             }f@                    dd4|fdw           |adk    ryg }f	 ddlmV}v d  |vd          pg D             }fn*# t          $ r t          K                    d           Y nw xY w|fr*t          |f|a          }w@                    |e|a|wdw           L|ad2k    rg }fg }x	 ddlmV}v d  |vd2          pg D             }xn*# t          $ r t          K                    d           Y nw xY wt                      D ]}j|j|xvr|x@                    |j           d |xD             }f|fs-t          jZ        tX                              d2g                     }f|fr*t          |f|a          }w@                    |e|a|wdw           2|ad!k    rg }fg }yd}zd}{	 ddlmV}v  |vd!          pg }|n.# t          $ r! t          K                    d           g }|d }{Y nw xY w||rt                              di           }}t          |}t                    r|}                    d          ppd }~t          |||~          \  }}d |D             }fd |D             }y|r#dt9          |           dt9          ||           d}znJ|{st          K                    d           n-t          jZ        tX                              d!g                     }f|frKt          |f|a          }w|yrt          |y|a          ng }|e|zz   |a|wdw}|r||d<   @                    |           |adRk    rqg }fg }	 ddlmV}v  |vdR          pg }n*# t          $ r t                              d           Y nw xY w|rd |D             }fnt                              dSi                               dRi           pi }t          dR          pd}t          |t                    r6t          |                    d^          pd          &                                nd}|r^ddi}|rd| |d<   |dz   ]                    d          }	 dd lLmM}l |lN                    |d|          }|lO                    |d          5 }t          j
        |P                                Q                                          }d d d            n# 1 swxY w Y   |                    dV          pg D ]n}t          |t                    rWt          |                    d          pd          &                                }j|jr|j|jdr|fvr|f@                    |j|jdr           on+# t          $ r t                              d|           Y nw xY w|fr*t          |f|a          }w@                    |e|a|wdw           	2|atX          v s|a|!v re|!                    |a|a          }t                              dSi                               |i           }6g }ft          |6t                    radn|6v r]|6dn         }t          |t                    rd |                                D             }fn!t          |t&                    rd |D             }f|fst          |at          |a                    }f|fs-t          jZ        tX                              |ag                     }f|*                    |ag           }|r|fst          jZ        |          }ft          |f|a          }w@                    |e|a|wdw           
|*                    |a          }|rt          jZ        |          }n*|)r&|adbk    rr	dbk    rg }nt          jZ        |)          }ng }|r@                    |e|a|dw           |adbk    r|r@                    |e|ag dw           ;n/r-t                    }@                    dd|drgdw           r[t                    &                                6                                `                    dd          tZ          v ptW                    tZ          v }|rt          K                    d           nوfdD             }           |vrt                    }r/tZ                              pd          6                                nd}d}D ]S}|rO|                    dd          6                                |k    r#|dn         a                    d|dr           d } nT|s!r@                    dpd|drgdw           t                     d D             t                      	 t          |t                    r|                    di           ni }t          |t                    r4|D ]1}	                    t+          t          |	                               2n# t          $ r Y nw xY w	 t                              dSi           } t          | t                    r|                                 D ]s\  }}t          |t                    rY|                    d^          s|                    dm          r/                    t+          t          |                               tn# t          $ r Y nw xY wfd}c                    |           i }	 t                              di                               di           }t          |t                    rd |                                D             }n# t          $ r Y nw xY w |             |dS )Nr<  r   c                 N   t          | pd                                                                          }|                    d          r#d|v r|                    d          }|d         p|}d|v r|                    d          }|d         p|}|                    dd          S )Nr
   r  rV   r  r  ra  r  )r4   r   r   rb  r  r  )r<  r  partss      r   _norm_model_idzVget_available_models.<locals>._build_available_models_uncached.<locals>._norm_model_id?  s    HN##))++1133A ||C   #SAXX"IN axx"IN99S#&&&r   c                     g } rr|                      ddd           t                              dg           }t          |t                    rt          |d          D ]\  }}t          |t                    st          |                    d                    t          |                    d          pd	          	                                }r|sx|                      |d
d| d           d D             }d |D             d D             i }|D ]4}|
                     |          g                                |           5i }| D ]&}|d         |d         }g }	| d| d d| fD ]}
|
r|
|	vr|	                     |
           d }t          fd|	D             d           }|	D ])}
|
v r#                    |
          k    r
|
         } n*|V|	D ]S}
 |
          }|                    |g           }|s&t          fd|D             d           }|p	|p|d         }|r nT|d         |d         d}|	D ]%}
                    |
          }|r|k    r |||
<   &|r|||<   (|S )NprimaryPrimary)r  r  roler  fallback_providersr    )startr  r  r
   fallbackz	Fallback c                     g | ]F}|                     d g           D ]-}|                     d          |                     dd          .GS )r  r  r
   r  rk  rk  r)   s      r   rm  zzget_available_models.<locals>._build_available_models_uncached.<locals>._build_configured_model_badges.<locals>.<listcomp>k  sU    fffa155SUCVCVffaZ[Z_Z_`dZeZef!%%b//ffffr   c                 H    i | ]}t          |          t          |           S rc   )r4   )rk  opt_ids     r   r  zzget_available_models.<locals>._build_available_models_uncached.<locals>._build_configured_model_badges.<locals>.<dictcomp>l  s&    OOO&S[[#f++OOOr   c           	          i | ]u}|                     d g           D ]\}|                     d          t          |                     d                    t          |                     d          pd          ]vS )r  r  r  r
   )r}   r4   r  s      r   r  zzget_available_models.<locals>._build_available_models_uncached.<locals>._build_configured_model_badges.<locals>.<dictcomp>m  s     & & &x,,& & 55;;	&AEE$KK  #aeeM&:&:&@b"A"A& & & &r   r  r  rV   c              3   0   K   | ]}|v |         V  d S r  rc   )rk  coption_lookups     r   rv  zyget_available_models.<locals>._build_available_models_uncached.<locals>._build_configured_model_badges.<locals>.<genexpr>  s4      #c#cPQUbPbPbM!$4PbPbPbPb#c#cr   c              3   N   K   | ]}                     |          k    |V   d S r  r  )rk  r)   option_provider_lookupr  s     r   rv  zyget_available_models.<locals>._build_available_models_uncached.<locals>._build_configured_model_badges.<locals>.<genexpr>  s=      ]]13I3M3Ma3P3PT\3\3\Q3\3\3\3\]]r   r   r  r  )r  r  r  )r1   ra   r}   r{   rn  r
  r|   r^  r4   r   r  next)configured_entriesfallback_cfgidxrl  r  
option_idsrj  r  badgesraw_candidatesr   match_idexact_matchr   matchesprovider_matchbadge_payloadcandidate_providerr  r  r  r  r  rv  r  s                     @@@r   _build_configured_model_badgeszfget_available_models.<locals>._build_available_models_uncached.<locals>._build_configured_model_badgesN  s   79 = "))$3!. )!*	    77#7<<L,-- "+L"B"B"B  JC%eT22 ! 6uyy7L7LMMH		' 2 2 8b99??AAE# !5 ! &--(0%*$.%6%6%6	     gf6fffJOOJOOOM& && & &" 13K$ R R&&~~f'='=rBBII&QQQQ02F+ '5 '5 ,g!#))%))***5**" 9 9I
 ! 9Yn%D%D&--i888"#c#c#c#cn#c#c#ceijj!/  I M116L6P6PQZ6[6[_g6g6g#0#;#%3 " "	%3^I%>%>
"-//*b"A"A& %$)-]]]]]]]] * * $2#N[#NGAJ# "!E" */vw]e f f!/ 6 6I)?)C)CI)N)N&) !.@H.L.L (5F9%% 5'4F8$Mr   r
   r  r  rY   r~  r~  r   r   r   r  z!Failed to load auth store from %scredential_pool)	load_poolr  c                     g | ]o}t          t          t          |d d          pd          t          t          |dd          pd          t          t          |dd          pd                    m|pS )ro  r
   r  r  )r  r4   r  )rk  r   s     r   rm  zRget_available_models.<locals>._build_available_models_uncached.<locals>.<listcomp>  s     ) ) )&''?$'8R(@(@(FB$G$G$'7B(?(?(E2$F$F$'<(D(D(J$K$K(" (") !) ) )r   z$credential_pool.load_pool(%s) failedc              3   6  K   | ]}t          |t                    ozt          t          |                    d d          pd          t          |                    dd          pd          t          |                    dd          pd                     V  dS )ro  r
   r  r  N)r{   r|   r  r4   r}   )rk  rV  s     r   rv  zQget_available_models.<locals>._build_available_models_uncached.<locals>.<genexpr>  s       1 1 !' 'vt44 $< #FJJx$<$<$B C C #FJJw$;$;$Ar B B #FJJ|R$@$@$FB G G% % !1 1 1 1 1 1r   z1Failed to inspect credential_pool from auth storeF)list_available_providers)get_auth_statusauthenticatedr  r  r  z(Failed to get key source for provider %sunknownTr%  	logged_inz'Failed to check Nous Portal auth statusz+Failed to detect auth providers from hermesrg   r   #=r    "'zFailed to parse hermes env file)ANTHROPIC_API_KEYOPENAI_API_KEYOPENROUTER_API_KEYGOOGLE_API_KEYGEMINI_API_KEYGLM_API_KEYKIMI_API_KEYDEEPSEEK_API_KEYXIAOMI_API_KEYOPENCODE_ZEN_API_KEYOPENCODE_GO_API_KEYMINIMAX_API_KEYMINIMAX_CN_API_KEYXAI_API_KEYMISTRAL_API_KEYAWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYr  r'  r  r(  r)  r  r&  r  r2  r  rI  r  r-  r  r.  r  r0  r  r1  r  r/  r  rB  r  r@  r  r>  r  r;  r  r<  r  r  rC  
LM_API_KEYLM_BASE_URLr=  r5  c                    t          |           }|sdS t          t                    rt                              d                    }||k    r_t	                              d          t
          |           }|r3t          |                                                                          S t
                              di           }t          |t                    r|	                                D ]\  }}t          |t                    st          |                    d                    }||k    rFt          |          }|r5t          |                                                                          c S t
                              dg           }t          |t                    r|D ]}	t          |	t                    st          |	                    d                    }
|
|k    rAt          |	                    d          pd                                          }|rt          |          c S  dS dS )	Nr
   r~  r  r  r5  rh  r   rW  )r  r{   r|   r}   r  ra   r4   r   r   r  r^  rn  re  )r~  r  model_base_urlrM  rg  provider_keyprovider_cfgprovider_base_urlcustom_providers_cfgrl  r  r{  r8  s               r   !_configured_provider_for_base_urlziget_available_models.<locals>._build_available_models_uncached.<locals>._configured_provider_for_base_url  sB   28<<F r)T** 	B!>y}}Z?X?X!Y!Y!V++$C!j11!)% % %M
 % B"=117799??AAAGGK44M-.. 
F2?2E2E2G2G 	F 	F.L,%lD99 ! (E$((44) )% )F22(?(M(M( F#&}#5#5#;#;#=#=#C#C#E#EEEE#&77+=r#B#B .55 
$1 	$ 	$E%eT22 ! %B599ZCXCX%Y%YN%// !$UYYv%6%6%<"!=!=!C!C!E!EJ! K>zJJJJJ#882r   c                     t          | pd                                                              d          }|                    d          r|dz   S |dz   S )Nr
   r  z/v1/modelsz
/v1/models)r4   r   r  r_  )r~  r  s     r   _models_endpoint_for_base_urlzeget_available_models.<locals>._build_available_models_uncached.<locals>._models_endpoint_for_base_url  sW    x~2&&,,..55c::D}}U## (i'',&&r   rR   c                 .   g }t          | t                    rOd| v r$t          | d         t                    r	| d         }n'd| v r#t          | d         t                    r| d         }g }t                      }|D ]}t          |t                    s|                    dd          p+|                    dd          p|                    dd          }|                    dd          p|                    dd          p|}t          |pd                                          }t          |pd                                          }|r|r||v r|                    |           |dv rt          |          n|}|	                    ||d           |S )	NrR   r  r  r
   r   r  ru  r  )
r{   r|   rn  r  r}   r4   r   r   r  r1   )	rR   r  models_listr  r  r  r<  
model_namer  s	            r   #_extract_model_entries_from_payloadzkget_available_models.<locals>._build_available_models_uncached.<locals>._extract_model_entries_from_payload  s   K$%% 1T>>jft&D&D>"&v,KK%%*T(^T*J*J%"&x.KF55D$ @ @!%.. IIdB'' .yy,,.yy"-- 
 #YYvr22Xeii6L6LXPX
x~2..4466 !1r2288::
 z X5E5E""":BF`:`:`,X666fpX>>????Mr   coder   r  c                $   t          | pd                              dd          }||nt          |dd           }|dv rdt          |          d| d| d	d
S t	          |t                    rdt          |          d| d| dd
S dd d| dd
S )NrW  r`  r
   r  )i  i  authzModels endpoint returned u    — check the API key for r  )kindr  messager  z for z; see logs.networkz Models endpoint unreachable for z; verify base_url.)r4   r  r  r&   r{   )r  r   r  provider_labelstatus_codes        r   _custom_endpoint_errorz^get_available_models.<locals>._build_available_models_uncached.<locals>._custom_endpoint_error  s     !!5X66>>y"MMN"&"2$$VT8R8RKj((",,t;ttcqttt  
 +s++ ",,h;hh^hhh   "`n```  r   rc   )r  trusted_base_urlsr  r  .c          
         t          | pd                                          }|sg d fS 	 dd l}dd l}dd l}dd l} |          }i }	t          |pd                                          }
|
rd|
 |	d<   t                      }|g|R D ]g}t          dt          |          v rt          |          nd|           }|j        r,|	                    |j        
                                           ht          d|v r|nd|           }|j        dvrt          d|j                   |j        r	 |                    |j        d           }|D ]\  }}}}}|                    |d                   }|j        s|j        s|j        rT|j        pd
                                t%          fd	d
D                       p|v }|st          d|d                    n# |j        $ r Y nw xY w|j                            |d          }|                    dd           |	                                D ]\  }}|                    ||           |j                            |d          5 }t3          j        |                                                    d                    }d d d            n# 1 swxY w Y    ||          d fS # |j        j        $ rG} ||t?          |dd                     }t@          !                    d||           g |fcY d }~S d }~wtD          $ r6} ||          }t@          !                    d||           g |fcY d }~S d }~ww xY w)Nr
   r   Bearer Authorizationr  r  )r
   r  r  zInvalid URL scheme: c              3       K   | ]}|v V  	d S r  rc   )rk  r  host_ls     r   rv  zwget_available_models.<locals>._build_available_models_uncached.<locals>._read_custom_endpoint_models.<locals>.<genexpr>;  s;       5" 5"() %&K5" 5" 5" 5" 5" 5"r   )r9  r#  r   r=  r  z&SSRF: resolved hostname to private IP GET)method
User-AgentOpenAI/Python 1.0
   timeoutr   r  r  z7Custom endpoint models fetch failed for provider %s: %sz@Custom endpoint unreachable or misconfigured for provider %s: %s)#r4   r   r%  urllib.errorurllib.requestsocketr  r   r  r   r   r  r'  getaddrinfor&  r)  r(  r*  r  gaierrorrequestRequest
add_headerr  urlopenrZ   r$  readdecodeerror	HTTPErrorr  r$   r   r\   )r~  r  r  r  r  r%  urllibr  endpoint_urlheadersr  _ssrf_trusted_hoststrusted
_cp_parsedr  resolved_ipsr  r+  addr_objis_known_localreqr  r  responserR   r   r  r  r  r  r  s                              @r   _read_custom_endpoint_modelszdget_available_models.<locals>._build_available_models_uncached.<locals>._read_custom_endpoint_models  s    x~2&&,,..D  4x8!    ####%%%%<<TBB'-R((..00 ?/>GO,
 14# $9'899 M MG!)(-W(=(=GCVWCVCV" "J "* M+//
0C0I0I0K0KLLL%e|6K6KllQi[gQiQijj
$,AAA$%OJ<M%O%OPPP& '-'9'9*:Mt'T'T0< 	i 	i,Aq!Q'0';';DG'D'DH'2 ih6J ihNd i*4*=*C)J)J)L)L14 5" 5" 5" 5"-j5" 5" 5" 2" 2" 2C &,/B%B !/ (6 !i*45g^bcd^e5g5g*h*h$h	i "?    n,,\%,HH|-@AAA#MMOO ) )DAqNN1a((((^++C+<< G:hmmoo&<&<W&E&EFFDG G G G G G G G G G G G G G G::4JJDPP<) ! ! !..x73PVX\C]C]^^^VX`bghhh5y       ! ! !..x==_aikpqqq5y      !sz   DK 3B*G K 
G+(K *G++BK ,:J2&K 2J66K 9J6:K M<LMM)+MMMrW  r  r  r9  r   r#  r  )HERMES_API_KEYHERMES_OPENAI_API_KEYr  LOCAL_API_KEYr  API_KEYrh  c              3      K   | ]C}t          |t                    |                    d           ,|                    d           V  DdS )r~  Nr{   r|   r}   rk  _cps     r   rv  zQget_available_models.<locals>._build_available_models_uncached.<locals>.<genexpr>  sh       - -!#t,,- 251D1D-GGJ''- - - - - -r   r   rc  r  rV   r  r  r  r`  c              3      K   | ]C}t          |t                    o)|                    d           pd                                 V  DdS )r   r
   N)r{   r|   r}   r   r  s     r   rv  zQget_available_models.<locals>._build_available_models_uncached.<locals>.<genexpr>  sc         3%%Mswwv/D".K.K.M.M*M     r   only_configuredc              3   8   K   | ]}t          |          p|V  d S r  )r  rk  r  s     r   rv  zQget_available_models.<locals>._build_available_models_uncached.<locals>.<genexpr>3  sA       , ,:;-a005A, , , , , ,r   )r  r  r  models_endpoint_error)fetch_openrouter_modelsz9Failed to load OpenRouter curated catalog from hermes_cliz#https://openrouter.ai/api/v1/modelsAcceptr   )r  g       @r     pricingprompt0
completionz:freer  r  z(free)z (free)z;OpenRouter free-tier live fetch unavailable; using fallbackc                 b    g | ],}|                     d           dk    |d         |d         d-S )r  r$  r  r  r  r  r?  s     r   rm  zRget_available_models.<locals>._build_available_models_uncached.<locals>.<listcomp>  sI     & & & ! uuZ00L@@ $%T7QwZ@@@@@r   r$  r:  rn  c                 2    g | ]}|t          |          d S r  )r  rk  r  s     r   rm  zRget_available_models.<locals>._build_available_models_uncached.<locals>.<listcomp>  s9     & & & # $'1Ec1J1JKK& & &r   z2Failed to load Ollama Cloud models from hermes_clic                     g | ]}||S rc   rc   r  s     r   rm  zRget_available_models.<locals>._build_available_models_uncached.<locals>.<listcomp>  s    $g$g$gScf$gS$g$g$gr   z2Failed to load OpenAI Codex models from hermes_clic                 4    g | ]}|t          |g           d S r  )rl  r  s     r   rm  zRget_available_models.<locals>._build_available_models_uncached.<locals>.<listcomp>  s;     " " "  #-A#r-J-JKK" " "r   z1Failed to load Nous Portal models from hermes_cli)r  c                 8    g | ]}d | t          |          dS r  r  r  r  s     r   rm  zRget_available_models.<locals>._build_available_models_uncached.<locals>.<listcomp>-  s?     & & & # $2C>><Ns<S<STT& & &r   c                 8    g | ]}d | t          |          dS r#  r$  r  s     r   rm  zRget_available_models.<locals>._build_available_models_uncached.<locals>.<listcomp>1  s?     ( ( ( # $2C>><Ns<S<STT( ( (r   r  z of r  us   Nous Portal authenticated but live-fetch returned empty — omitting from picker (will retry on next cache rebuild)extra_modelsz?hermes_cli LM Studio lookup unavailable; using urlopen fallbackc                     g | ]}||d S r  rc   r  s     r   rm  zRget_available_models.<locals>._build_available_models_uncached.<locals>.<listcomp>q  s     %R%R%RCS3&?&?%R%R%Rr   r  r  r  r  r  r  )r  r     z$LM Studio /models fetch failed at %sc                     g | ]}||d S r  rc   r  s     r   rm  zRget_available_models.<locals>._build_available_models_uncached.<locals>.<listcomp>  s     )[)[)[AQ*?*?)[)[)[r   c                     g | ]U}t          |t                    r|d          n|t          |t                    r|                    d|d                    n|dVS )r  r  r  r  r  s     r   rm  zRget_available_models.<locals>._build_available_models_uncached.<locals>.<listcomp>  s     *@ *@ *@/0 =Gq$<O<O1V4UVPZ[\^bPcPc5jQUU7AdG5L5L5Lij+l +l *@ *@ *@r   r  r  ra  u   Suspicious model.default value %r — looks like a provider id, not a model id. Skipping picker injection. Check `model.default` in config.yaml.c                 d    h | ],}|                     d g           D ]} |d                   -S )r  r  r  )rk  rk  r)   r  s      r   rt  zQget_available_models.<locals>._build_available_models_uncached.<locals>.<setcomp>  sG    eeeAQRQVQVW_acQdQdeeAqw 7 7eeeer   c                     g | ]C}|                     d           s*|                     d          pd                    d          A|DS )r  r  r
   r`  r}   rb  )rk  rk  s     r   rm  zRget_available_models.<locals>._build_available_models_uncached.<locals>.<listcomp>2  sa     
 
 
uuX
 m$$*66yAA

 
 
r   c                     |                      d          pd}|k    rd|fS |                    d          rd|fS |v rd|fS d|fS )Nr  r
   r   r`  r    r  r  r-  )rk  r  _providers_with_keysr  s     r   _group_sort_keyzWget_available_models.<locals>._build_available_models_uncached.<locals>._group_sort_keyK  sh    %%&&,"Co%%3x~~i((  3x***3xs8Or   r  aliasesc                     i | ]J\  }}||	t          |                                          t          |                                          KS rc   )r4   r   r  s      r   r  zRget_available_models.<locals>._build_available_models_uncached.<locals>.<dictcomp>[  sF     k k kDAqcd kij kQQ k k kr   )r  rv  r3  r  r1  )dry  ra   r4   r|   r}   r{   r  r  r8   rZ   r$  r   r\   r$   r   r  r   agent.credential_poolr  rn  rB  r^  r  ra  rp  	monotonicrj   r  r   r  r\  r  hermes_cli.authr  ri   rh   r5   
splitlinesr   rb  r  r   r   r  r  r  r  objectr&   tupler  r   r  r=   r   r  r%  r&  r)  r(  r*  r'  filterextendr1   re  rl  discardrw  r  r   intersectionr  titler  r%   r  r  r  r  r  r  r~  	TypeErrorr_  _FALLBACK_MODELSro  r  r  rQ  rR  r  r;  r  rw  rs  r  insertr  r  )r  cfg_base_urlrw  
auth_storeauth_store_pathrC  detected_providers_pool
_load_pool_pid_canonical_pid_cached_cp_ts_cp_pool_all_entries_lp_t0	_explicit_entries_has_explicit_credall_env_hermes_auth_used_lap_gas_p_src_gah2hermes_env_pathenv_keysliner  r  val_cfg_providers_canonical_to_raw_provider_key_pid_key_provider_cfg
_canonical_is_known_provider_is_provider_configr  r  auto_detected_models auto_detected_models_by_providerr~  configured_providerr  provider_from_configparsedr  r%  r+  r  rg  r  r  api_key_varsr  _trusted_custom_bases_custom_providers_for_trust_active_endpoint_models_active_endpoint_error
auto_model_custom_providers_cfg_named_custom_groups_named_custom_errors_seen_custom_idsr  _cp_namerW  _cp_base_url_cp_api_key_cp_key_env_cp_configured_models_cp_has_configured_models_live_models_live_error_live_model_live_id
_dedup_key_cp_option_id_cp_model_ids	_cp_model_cp_models_dict_m_id_item_mid	_cp_label_has_custom_providers_has_unnamed_named_custom_slugs_base_matched_named_slug	_pid_normonly_show_configuredconfigured_providerscfg_providers_canonicalised_detected_cr  _nc_display
_nc_modelsr'   r  r  seen_ids_fetch_or_modelslive_curatedr  _desc_urlreq_req_resp_payload_free_count	_free_cap_pricing_is_free_name_labelrp  r  	codex_idsr&  truncated_label_suffixlive_fetch_failedr  
_model_cfg	_selectedfeatured_ids
extras_idsr  group_entrylm_idslm_cfglm_base_url
lm_api_keyr  endpointr  resplm_datar)   _raw_key
cfg_modelsdetected_modelsmodels_for_groupr  _looks_like_provider_idall_ids_normtarget_displayinjectedrk  _pk_pvr0  model_aliasesraw_aliasesr  r  r  r  r/  r  rv  r  r8  s                                                                                                                                                               @@@@@@@@@r    _build_available_models_uncachedz>get_available_models.<locals>._build_available_models_uncached:  s.   3C88	'S 	'S 	' 	' 	' 	'R	S$sCx.5H0I R	 R	 R	 R	 R	 R	 R	 R	 R	j GGGR((	i%% 	,	4(( 	,'mmJ77O#--	266K$==R88L , +  	=%  O 
.00!!## 	SS!!!!XXo&?&?&?&Q&QRR
& &E"'899!-' ' 'O
  S S S@/RRRRRS !UU 	4""?3335	N=G
TX=Y=YaJNN#4b999_aE%&& 1W5 1W0WMMMMMM $UZZ\\ 2 2 W WW-DSYY-O-ON&<&@&@&F&FG&23: 0$(IKK&$8G#C#C3;3C3C3E3ELL-1^-=-=F/9z$/?/?HDHIKKQYCZ$:4$@3;3C3C3E3ELL)-)9)9+5:d+;+;@D	X?V 6t </7/?/?/A/A) )+7) ) )I  ) G 2 6 6~ F F F( W W W"LL)OQUVVVVVW;W> # W W W*/++-- W Wh)(D99 %S]]a=O=O$-0 1 1 +31 1 1 . .* . W.223J3t993U3UVVVW WW  	N 	N 	NLLLMMMMM	N ! 	HJJJJJJ??????dff 	1 	1vvo.. f44>>--lB??D..  /  f f fLL!KRVVTXZcMdMdeeeeef"&&r$x0000 $H4<<##K00 3&**6222 H H HFGGGGGH 	H 	H 	HLLFGGGGG	H ! Q	3@HHHHHH"'%''F"2 @ @ @"6"?@H%%'' DD / 9 97 9 K K V V X X R R#zz|| R(<(< R#'::c1#5#5DAq23''))//#2F2F2L2LS2Q2QHQWWYY/	R
 ! D D DLL!BCCCCCD"lG % %& ill %!$GAJ{{.// 4"&&{333{{+,, 7"&&x000
 #&&~666{{/00 5"&&|444{{+,, 1"&&x000{{+,, 1"&&x000{{=)) ."&&u---{{>** 6"&&}555{{,-- 2"&&y111{{/00 5"&&|444{{-.. 3"&&z222{{+,, 1"&&x000{{=)) /"&&v...{{,-- 4"&&{333{{122 7"&&~666{{011 6"&&}555 {{.// 2GKK@W4X4X 2"&&y111{{<(( 3W[[-G-G 3"&&z222& b11 :<&nd++ 	3+9+?+?+A+A 3 3'-6x@@
!  "22UjDU6U # '1&E&E#* .A .99*hOOO"&&z2222*	 *	3 *	 *	 *	 *	 *	 *	X	'C 	'C 	' 	' 	' 	'	f 	 	PTUYPZ 	 	 	 	>  $		 	 			 *		
 	 	 	 	< !46B	! B	! B	!B	!B	! 	B	!
  %VS[1B	! 4:td{*+B	! B	! B	! B	! B	! B	! B	! B	!J  "BD( L	5#))++H"C"CH"M"M*6hH#'(;#<#< %8*;*;hhAU8AUAUVVFM0V[7799D '; $$$$$//@@D 0$*: 0d>P 0#t++{d/B/BkUYFYFY'/HH'4//;$3F3F'1HH (0H!   D G)T** C$==339r@@BB & #R 8 8mT22 &(.tox5P(Q(Q & &'4'8'8r'J'J%lD99 &'3'7'7	'B'B'Hb&O&O&Q&QG& & %   (  C&{{3//G29S>>GRNNPPG  4@.!*-''2Db*I*I'5t<< %,, - -:- - -   
 ?[>Z"'(=">">	? ? ?;#%; 6 5 5
$++J777'~~//0;;L"MMTTU_```"&&|4444 #(:B ? ?%'02+T22 f	="uu, d= d=!#t,, GGFOO1r88::EMW7AAASW AU*>>>3;R.(/"377:#6#6#<"==CCEE 5\ 5"%cggi&8&8&>B"?"?"E"E"G"GK& T&)#'')*<*<*B&C&C&I&I&K&K& T*-bi.D.D.J*K*K*Q*Q*S*SK -0GGH,=,=)"#84,GG ; 566: . $D#G#G#N#NL"&K0  (/+-L%-4P4P(!$//;o	5 5 51k # 66A,U3*..u555'3  #&{t'<'<'B#C#C#I#I#K#K' %$(-%:%:%:%:
%)999$(,,Z888*..u555(0*e33M<T<TUX<Y<Y3,G,G,G,G,GM,U3A6==#0;??7;S;S;yWkltvxWyWyzz   
 ,.GGGR00	 4!((333"%''("3"3ot44 ;!0 @ @%eS11 @ekkmm @UbHbHb)00???@  66 	;!0 ; ;%eS11 ;#(;;==D# ;M(A(A - 4 4T : : :'t44 ;#&uyy'g%))G:L:L'gPUPYPYZ`PaPa'geg#h#h#n#n#p#pD# ;M(A(A - 4 4T : : :!. = =I;@!OE!7!7I!7!7!7iJ  =Z7G%G%G$8B$G$G	(,,Z888  
=.225999,5M.%77@X@XY\@]@]70KE0K0KM0K0K07:AA'4y I I    177yS\8]8]^^^.228<<<=" !++@$ G G jCPeLfLfijLj 	5(::CX:&&x000011 6 6##I.. 67L 6&..u5556 ((-B(  0    L   5"**8444:3??#KLZ]#^#^ # 	5(; 	5/00 5 5
OO113399;;	''	22 5yH[7[7[&..t444 R00NXYfhlNmNmx}001BEJJJsx 	W#&55  :$((999GGK44M-.. 
 %++ , ,?L?Q?Q?S?S, , ,    "4!@!@AU!V!V  	9&)ee#* 0 0.t44<'++B////!8  n	011 g g >>),, 2222Fs2K/Z  * W)I)M)McSU)V)VJ% 10D)D)D1<S\f$g$gE"&:::AUVYAZ&= >"MM%000 1 5 5c399;; G G,&& "$J"uuH
d      (8'7'9'9'?R*6 M MJC" Ms(':': (S 1 1 1 * 1 1s2K2K L L LM % d d d'bcccccd)d888888&A%-/A$B  /     %__T3_?? I5'+z%**,,2E2E2G2G'H'HHI I I I I I I I I I I I I I I&'$&	%-\\&"%=%=%C & &E#-eT#:#: ) (#&uyy'<"#=#=#C#C#E#ED#' )48+;+; (',yy';';'ArH1$)(,,x*E*E*L$M$MQR$R %[(-hll<.M.M.TQT(U(UYZ(Z !) %.z#: 1 1 1+01 (0'I4==3I3IH#+ ) ( #EIIf$5$5$; < < B B D D L " >AE\\U[[%5%5b%9%9uF'v||~~==,2);););$LL...&--TF.K.KLLL'1,K*i77 %  8$ d d d%bcccccd & & &%5& & &
 MM(4+7&0     N**!#J]______& &(;(;N(K(K(Qr& & &

 % ] ] ]'[\\\\\] " !7
C!Y!Y,9/2*0    N** "$J "I]______$g$g5H5H5X5X5^\^$g$g$g		$ ] ] ]'[\\\\\]  CDD 2 2i//%,,S111" "#," " "J
 & ]%)]3C3G3GXZ3[3[%\%\
! !7
C!Y!Y,9/2*0    F]] "$J/1L-/*(-%1______#6#6v#>#>#D"$ 1 1 1'Z[[[#%,0)))1
   6U &)WWWb%9%9
'
D99UjnnW>U>U $,$# "
 4L$.74 4 40j& &'3& & &
( ('1( ( ( &  !MS%6%6 L LCMM L L L 3 / U V    &*]3C3G3GPR3S3S%T%T
! 3!7
C!Y!Y `l!s!7c?![![![qs(58N(N+.&,' '
 " A:@K7k222J&& "$J(*Fh______!4!4Z!@!@!FB$ h h h%fgggggh  _%R%R6%R%R%R

 "%b!9!9!=!=j"!M!M!SQS&<Z&H&H&NBQ[\bdhQiQi%qSI)>)>)D"%E%E%K%K%M%M%Moq
& _'35H&IG) R;QZ;Q;Q 8(3i(?'G'G'L'LH_ @ @ @ @ @ @&-oohuV]o&^&^%,__S!_%D%D !O.2j9K9K9M9M.N.NG!O !O !O !O !O !O !O !O !O !O !O !O !O !O !O*1++f*=*=*C !Y !YA'1!T':': %Y.1!%%++2C.D.D.J.J.L.L+. )Y#3L3LT^3^3^,6,=,=SSV>W>W,X,X,X	!Y
 $- _ _ _ &-SU] ^ ^ ^ ^ ^_ " !7
C!Y!Y,9/2*0    ,,,7U0U0U  >AA#sKKH#&77;#;#;#?#?"#M#ML!#J ",55 @(l:R:R%1(%;
%j$77 @)[)[IZIZ)[)[)[JJ'
D99 @*@ *@4>*@ *@ *@J & %C9#>>& &

 & R%)]3C3G3GR3P3P%Q%Q
&F&J&J3PR&S&SO& Dz D%)]?%C%C
3J_UUFMM(5+.&,     'G&J&J3&O&OO& .+/=+I+I((- . (????V^C^C^/1,,/3}=Q/R/R,,+-('  ,9/2*:     \ (5+.&(' '   GgR  ,]FCC!*9XepuQvQvPwxx    *	 M""((**0022::3DDHYY Q,];;?PP $ ' & "	     feeeeee!>-00DD0GGE + )--o?TRTUU[[]]] #
  %H# " ") "aeeJ.C.C.I.I.K.K~.].]hK..qQV2W2WXXX'+H!E#  ,5/>/K)2?%+P+P*Q    	v&&&
 

 
 
 *-	=G
TX=Y=YaJNN#4b999_aE%&& Q! Q QD(,,-DSYY-O-OPPPP 	 	 	D		 WW["55N.$// T . 4 4 6 6 T THC!#t,, T#'')2D2D TPYHZHZ T,001HS1R1RSSS 	 	 	D		 	 	 	 	 	 	((( )+	'''2..229bAAK+t,, l k kKL]L]L_L_ k k k 	 	 	D	  /*'E'E'G'G$
 
 	
s  5AF %F76F7 AQ '*N/ D.N N/ %N+'N/ *N++N/ .Q /BQ	Q Q		Q $Q43Q4<.V ++SV 9TV T!V 63U* )V *$VV VV $V<;V<W W.-W.C[ $[21[2Aq. .
q;:q;\5AA^^$A^*^)A^*^.6Ag5_$9A`)`Ag5`)A`-	`-Ag5`0A`-	`1BAg5b>AAddAg5dAd'd$Ag5d&Ad'd'CAg5g5$AhhAhiAi4i4$AjjAjkAk5k5$AllAloAoo(App ApuAuu$Avv Avy5A}z9A{z;A}{A{	{A}{A{	{B	A}}%A~~ A~M3A6BO* O*
BO7O6BO7O;B8BR4 R4
BSS BSS#A"BU U
BUUBUNc                  $    t            ot          d uS r  )rW  r  rc   r   r   r  z&get_available_models.<locals>.<lambda>  s    44\9PX\9\ r   <   r  TF)rl   rp   rq   rr   rO   rP   rd   rs   r|   rW  r   r  rE  rV  rX  wait_forrT  ra  r4  r  r  r-  rN  rQ  rR  r\   rY  )	_current_path_current_mtimer  should_wait_cfg_changeddisk_groupscachedrO  r  s	            r   get_available_modelsr    s   &(**&++--6   (** 
:	%	%))C)C,.. *D 	k
d k
 k
 k
 k
b) +K.00116688A   !Z/L
 K&244	% 4% 4%  	$$\\ %    4DN4D4DEEF!4% 4% 4% 4% 4% 4% 4% 4%  	OOO&*#),&9=6K n/4414% 4% 4% 4% 4% 4% 4% 4%6 "&1#),&9Y9[9[6&{333=--A4% 4% 4% 4% 4% 4% 4% 4%F  	, 	,'+$	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	,	5577FF 	 	 	  - -+0(**,,,- - - - - - - - - - - - - - - 	  	) 	)&,#)-)9)9&9Y9[9[6',$&&(((	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	#6***}V$$i4% 4% 4% 4% 4% 4% 4% 4% 4% 4% 4% 4% 4% 4% 4% 4% 4% 4%s   '* AA2C CC=AK=K6KK!G0$K0G4	4K7G4	8K<
HKII 4I IIII
K?J"K"J&	&K)J&	*%KK #K r  z
index.htmld   c                       e Zd ZdZd Zdej        fdZdej        ddfdZde	e
ef         ddfd	Zdee
ef         fd
ZdS )StreamChannela{  Broadcast SSE events to every connected browser tab for a stream.

    While no tab is connected, events are buffered so the first/reconnected
    subscriber still receives the stream tail that arrived during the gap.
    Once one or more subscribers are attached, new events are broadcast to all
    of them instead of being consumed destructively by a single queue reader.
    c                 R    t          j                    | _        g | _        g | _        d S r  )	threadingLock_lock_subscribers_offline_bufferselfs    r   __init__zStreamChannel.__init__  s'    ^%%
/19;r   r   c                     t          j                    }| j        5  | j        D ]}|                    |           | j                            |           d d d            n# 1 swxY w Y   |S r  )queueQueuer  r  
put_nowaitr  r1   )r  qr}  s      r   	subscribezStreamChannel.subscribe  s    Z 	( 	( , # #T""""$$Q'''	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( s   :A!!A%(A%r  Nc                     | j         5  	 | j                            |           n# t          $ r Y nw xY wd d d            d S # 1 swxY w Y   d S r  )r  r  remover'  )r  r  s     r   unsubscribezStreamChannel.unsubscribe  s    Z 	 	!((++++   	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	s,   A%A
2A2AA	Ar}  c                 &   | j         5  t          | j                  }|s(| j                            |           	 d d d            d S | j                                         d d d            n# 1 swxY w Y   |D ]}|                    |           d S r  )r  rn  r  r  r1   r   r  )r  r}  subscribersr  s       r   r  zStreamChannel.put_nowait  s    Z 	) 	)t011K $++D111		) 	) 	) 	) 	) 	) 	) 	)
  &&(((	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	)  	 	ALL	 	s   2A,A,,A03A0c                     | j         5  t          | j                  t          | j                  dcddd           S # 1 swxY w Y   dS )zCReturn non-sensitive stream observation counters for health checks.)subscriber_countoffline_buffered_eventsN)r  r   r  r  r  s    r   diagnostic_snapshotz!StreamChannel.diagnostic_snapshot  s    Z 	 	$'(9$:$:+.t/C+D+D 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	s   )>AA)r   
__module____qualname____doc__r  r  r  r  r  r8  r4   r7  r  r|   r&   r  rc   r   r   r  r    s         < < <
5;    U[ T    uS&[1 d    T#s(^      r   r  c                      t                      S r  )r  rc   r   r   create_stream_channelr    s    ??r   STREAMSCANCEL_FLAGSAGENT_INSTANCESSTREAM_PARTIAL_TEXTSTREAM_REASONING_TEXTSTREAM_LIVE_TOOL_CALLSSTREAM_GOAL_RELATEDSTREAM_LAST_EVENT_IDPENDING_GOAL_CONTINUATIONACTIVE_RUNSLAST_RUN_FINISHED_AT	stream_idc                 .   | sdS t          j                     }t          |pi           }|                    d|            |                    d|           |                    dd           t          5  |t          | <   ddd           dS # 1 swxY w Y   dS )zAMark a WebUI agent worker as alive until its outer finally exits.Nr  
started_atphaserunning)ra  r|   r  ACTIVE_RUNS_LOCKr  )r  metadatarO  rl  s       r   register_active_runr  	  s     
)++CR  E	[),,,	\3'''	Wi(((	 ' '!&I' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' 's   2B

BBc                     | sdS t           5  t                              |           }||                    |           ddd           dS # 1 swxY w Y   dS )zAUpdate active-run metadata without creating a new run implicitly.N)r  r  r}   r   )r  r  rl  s      r   update_active_runr    s     	 # #	**LL"""# # # # # # # # # # # # # # # # # #s   2AAAc                     | sdS t           5  t                              | d           t          j                    addd           dS # 1 swxY w Y   dS )zCRemove a worker from the active-run registry and record idle start.N)r  r  r  ra  r  )r  s    r   unregister_active_runr     s     	 + +	4(((#y{{+ + + + + + + + + + + + + + + + + +s   /AAASESSION_AGENT_CACHE2   
session_idc                 d   d}t           5  t                              | d          }|t          |t                    r|d         nd}ddd           n# 1 swxY w Y   |dS d}	 ddlm}m}m}  ||           r || |d            ||           s ||            nd}n/# t          $ r" d}t                              d| d           Y nw xY w|r[t          |d	d          L	 |j                                         dS # t          $ r! t                              d
| d           Y dS w xY wdS dS )a  Remove a cached agent for a session (on delete, clear, or model switch).

    Attempts a lifecycle commit before dropping the agent handle so that
    batch-extraction memory providers can extract any pending work.  If the
    commit fails or there is uncommitted work with no successful commit, the
    lifecycle entry is preserved (not unregistered) so a future commit can
    retry.
    Nr   T)commit_session_memoryhas_uncommitted_workunregister_agent)r  waitFz*Lifecycle commit on eviction failed for %sr\  _session_dbz.Failed to close _session_db on eviction for %s)SESSION_AGENT_CACHE_LOCKr  r  r{   r8  api.session_lifecycler   r  r  r\   r$   r   r  r  close)r  r  rl  should_closer   r  r  s          r   _evict_session_agentr	  5  s    E	! C C#''
D99 *5% 8 8BE!HHdEC C C C C C C C C C C C C C C }L
^gggggggggg
++ 	F!!*EEEEE##J// 	!Z(((( L ^ ^ ^A:X\]]]]]^  f}d;;G	f##%%%%% 	f 	f 	fLLI:`dLeeeeee	ff fGGs5   =AAA%<B" ")CC%D   'D+*D+c                      | t           _        d S r  _thread_ctxenv)kwargss    r   _set_thread_envr  [  s    KOOOr   c                      i t           _        d S r  r  rc   r   r   _clear_thread_envr  _  s    KOOOr   SESSION_AGENT_LOCKSc                     t           5  | t          vrt          j                    t          | <   t          |          cddd           S # 1 swxY w Y   dS )aX  Return the per-session Lock used to serialize all Session mutations.

    Lock lifecycle invariant:
      - A Lock is created lazily on first access and lives in SESSION_AGENT_LOCKS
        for the lifetime of the session.
      - The entry is pruned in /api/session/delete (under SESSION_AGENT_LOCKS_LOCK)
        so deleted sessions don't leak a Lock forever.
      - During context compression the agent may rotate session_id.  The
        streaming thread migrates the lock entry atomically under
        SESSION_AGENT_LOCKS_LOCK: it aliases the new session_id to the *same*
        Lock object and pops the old-id entry (see streaming.py compression
        block).  This ensures that subsequent callers using the new ID still
        acquire the same Lock, while the old-id entry is removed to prevent a
        leak.  The streaming thread already holds the Lock during this
        migration, so the reference stays alive even after the dict entry is
        removed.
      - Lock contract: hold for the in-memory mutation + s.save() only; never
        across network I/O (LLM calls, HTTP requests).
    N)SESSION_AGENT_LOCKS_LOCKr  r  r  )r  s    r   _get_session_agent_lockr  h  s    ( 
" / /000.7n.>.>
+":./ / / / / / / / / / / / / / / / / /s   1AA
A
default_workspaceonboarding_completedsend_keyentershow_token_usageshow_quota_chiphide_empty_state_suggestionsshow_tpsfade_text_effectshow_cli_sessions show_previous_messaging_sessionssync_to_insightscheck_for_updatesignore_agent_updateswhats_new_summary_enabledthemedarkskinrY   	font_sizesession_jump_buttonssession_endless_scrollpinned_sessions_limitinflight_state_max_sessions   inflight_state_max_messages   inflight_state_max_tool_calls0   inflight_state_max_string_charsi`  inflight_state_max_json_charsi` hidden_tabslanguageenbot_nameHERMES_WEBUI_BOT_NAMEHermessound_enabledrtlnotifications_enabledshow_thinkingsimplified_tool_callingapi_redact_enabledcompactr  r  )sidebar_densityauto_title_refresh_everybusy_input_modepassword_hash>   bubble_layoutrv  assistant_language>   r&  lightsystem>   geist-contrastaresmonor%  slatesiennarY   poseidonsisyphus	charizard
catppuccin)r&  rL  )r&  rN  )r&  rO  r&  rY   )rL  	solarizedmonokainordoledc                    t          | t                    r&|                                                                 nd}t          |t                    r&|                                                                nd}t                              |          }|r|\  }}n|t          v r|d}}nd\  }}|t          v r|n|}||fS )uC  Normalize a (theme, skin) pair, migrating legacy theme names.

    Legacy migration table (from `_SETTINGS_LEGACY_THEME_MAP`):

        slate     → ("dark", "slate")
        solarized → ("dark", "poseidon")
        monokai   → ("dark", "sisyphus")
        nord      → ("dark", "slate")
        oled      → ("dark", "default")

    Unknown / custom theme names fall back to ("dark", "default").  This is a
    behavior change vs. the pre-PR-#627 state, where the `theme` field was
    open-ended ("no enum gate -- allows custom themes").  Users who set a
    custom CSS theme via `data-theme` will need to re-apply via skin or
    custom CSS — see CHANGELOG entry for details.

    The same mapping is mirrored in `static/boot.js` (`_LEGACY_THEME_MAP`)
    so client and server normalize identically; keep them in sync.
    r
   rY   rR  )r{   r4   r   r   _SETTINGS_LEGACY_THEME_MAPr}   _SETTINGS_THEME_VALUES_SETTINGS_SKIN_VALUES)r%  r'  	raw_themeraw_skinlegacy
next_themelegacy_skin	next_skins           r   _normalize_appearancera    s    ( *4E3)?)?G##%%%RI'1$'<'<Dtzz||!!###"H'++I66F 4"(
KK	,	,	,"+YK

 #4
K ,,, 	 
 y  r   c                     t          t                    } d}	 t                                          }n2# t          $ r% t
                              dt                     d}Y nw xY w|r	 t          j        t          	                    d                    }t          |t                     r1|                     d |                                D                        n0# t          $ r# t
                              dt                     Y nw xY wt          t          |t                     r|                    d          n|                     d          t          |t                     r|                    d	          n|                     d	                    \  | d<   | d	<   t!                      | d
<   	 t#                                          di           }t          |t                     r:|                    d          r%t%          |                    d                    | d<   n*# t          $ r t
                              d           Y nw xY w| S )zDLoad settings from disk, merging with defaults for any missing keys.Nz,Cannot stat settings file %s (inaccessible?)Fr   r   c                 ,    i | ]\  }}|t           v||S rc   )_SETTINGS_LEGACY_DROP_KEYSr  s      r   r  z!load_settings.<locals>.<dictcomp>  s4        Aq$>>> 1>>>r   zFailed to load settings from %sr%  r'  rv  r  r  default_model_providerz5Failed to resolve default model provider for settings)r|   _SETTINGS_DEFAULTSSETTINGS_FILEr8   rr   r$   r   rZ   r$  r   r{   r   r  r\   ra  r}   ry  rw   r4   )settingsstoredsettings_existsr8  s       r   load_settingsrk    sS   &''HF '..00       	C]SSS	 
  K	KZ 7 7 7 I IJJF&$''  $*LLNN      	K 	K 	KLL:MJJJJJ	K*?)&$77R

7X\\'=R=R(66P

6HLL<P<P+ +'HWx' !< = =H_NLL$$Wb11	i&& 	P9==+D+D 	P14Y]]:5N5N1O1OH-. N N NLMMMMMNOs5   2 ,A! A!'A3C *DD0A1H" "$I	I	rD  rv  z
ctrl+enterdetailed>   largesmallxlargerY   >   r  51020>   r  steer	interrupt)r  rA  r(  rB  rC  )r    c   )r    r  )r    r  )r       )i  i  )i i 	= )r+  r,  r.  r0  r2  r3  >   r;  r  r=  r:  r  r  r  r!  r"  r  r?  r#  r  r)  r<  r*  r>  r$  r  r   r!   z$^[a-zA-Z]{2,10}(-[a-zA-Z0-9]{2,8})?$rh  c                     t                      }|                    d          }|                    d          }d}d}d}|                     dd          }|rQt          |t                    r<|                                r(ddlm}  ||                                          |d<   d	}|                     d
d          rd|d<   d	}|                                 D ]\\  }	}
|	t          v rL|	dk    r.t          |
t                    r|
                                r|
}d	}D|	dk    r.t          |
t                    r|
                                r|
}d	}x|	t          v r|
t          |	         vr|	t          v rE	 t          |
          }
n# t          t          f$ r Y w xY wt          |	         \  }}|
|k     s|
|k    r|	dk    r1t          |
t                    rt                              |
          s|	dk    r#t          |
t"                    s3d |
D             }
|	t$          v rt'          |
          }
|
||	<   ^|}|}|rJ|sHt          |t                    r&|                                                                nd}|t*          vrd}t-          ||          \  |d<   |d<   t	          t/          |                    d                              |d<   d |                                D             }t0          j                            d	d	           t0                              t9          j        |dd          d           |rddlm}  |             d|v rt/          |d                   atA                      |d<   |S )zISave settings to disk. Returns the merged settings. Ignores unknown keys.r%  r'  F_set_passwordNr   )_hash_passwordrD  T_clear_passwordr5  r4  c                 l    g | ]1}t          |t                    r|                                r|d v/|2S )>   chatrh  rA  )rk  r  s     r   rm  z!save_settings.<locals>.<listcomp>y  sP       !!S))./ggii<=EY<Y<Y <Y<Y<Yr   r
   r  c                 &    i | ]\  }}|d k    ||S )rv  rc   r  s      r   r  z!save_settings.<locals>.<dictcomp>  s(    JJJ$!QQ/5I5IA5I5I5Ir   r   r  r  rH  r   r   )_invalidate_password_hash_cacherv  )!rk  r}   r  r{   r4   r   api.authry  r  _SETTINGS_ALLOWED_KEYS_SETTINGS_ENUM_VALUES_SETTINGS_INT_RANGESr&   r>  r'  _SETTINGS_LANG_REr"   rn  _SETTINGS_BOOL_KEYSr  r   rY  ra  r   rg  r7   r   r   rZ   r[   r  r   ry  )rh  currentpending_themepending_skintheme_was_explicitskin_was_explicit_password_changedraw_pwry  r  r  	min_value	max_valuetheme_value
skin_valuer[  	persistedr  s                     r   save_settingsr  @  s   ooGKK((M;;v&&L\\/400F !*VS)) !fllnn !++++++#1>&,,..#A#A  ||%u-- !#'     , ,1&&&G||a%% .!'')) .$%M)-&F{{a%% -!'')) -#$L(,%)))a7LQ7O.O.O(((AAA!:.   H';A'>$	9y==A	MMJq#&& .?.E.Ea.H.H   M!!!!T**     
 '''GGGAJKJ "3 5?s5S5S[M''))//111Y[	222J(=k:(V(V%GGgfo#&!'++.A"B"BCC$ $G  KJ'--//JJJItd;;;
95;;;      *<<<<<<''))) g%%5g>Q6RSS:<<GONs   4FFFr   r  r~  r   r   SESSIONS)init_profile_stater  )r   N)NNN)r  collectionsrQ  r   rZ   loggingr   r  r!   r  r  ra  	tracebackuuidpathlibr   r$  r   r   homer   __file__r7   r3   r6   r   r   r   r&   r   r   TLS_CERTTLS_KEYTLS_ENABLEDr5   r4   r2   r   SESSION_DIRWORKSPACES_FILESESSION_INDEX_FILErg  LAST_WORKSPACE_FILEPROJECTS_FILE	getLoggerr   r$   r+   r>   rM   r   r   r=   r1   r   r`   r  r   rO   r~  __annotations__rP   rQ   r|   r^   r  rd   rl   r   r~   rw   r   rs   r   r   ra   rn  r   r   r   r   r   ru  r   r8  r   MAX_FILE_BYTESMAX_UPLOAD_BYTES
IMAGE_EXTSMD_EXTS	CODE_EXTSMIME_MAPr  r  r  r  CLI_TOOLSETSr?  r  r[  r^  r7  re  rq  r  rw  r|  r  r  r  r  r  r  r  r  r  r  	frozensetr  r  r  r  r  _NOUS_FEATURED_TARGETr  r  r  r  r   r!  r,  r3  r;  rY  rl  ro  rp  ry  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  RLockrV  	ConditionrX  rW  r  r  r  r9  r0  r  r  r  r  r  r(  r-  r   r4  r?  rE  rN  rT  r  r_  rb  rl  rs  rw  r  r  _INDEX_HTML_PATHLOCKSESSIONS_MAX	CHAT_LOCKr  r  r  STREAMS_LOCKr  r  r  r  r  r  r  r  r  r  r  SERVER_START_TIMEr  r  r  OrderedDictr  SESSION_AGENT_CACHE_MAXr  r	  rY  r  r  r  r  r  r  rf  rd  rY  rZ  rX  ra  rk  rB  r  r  r  r  r   compiler  r  _startup_settingsr8   _settings_file_existsrr   r}   r  r   r[   r\   r  ri   r  rj   rc   r   r   <module>r     s:(  	 	 	         				  				 



                 + + + + + + + + ty{{DNN!(0022	
t 
 
 
 
 ry$k22
s929(&1122 29,b117799AT
")*B
/
/
5
5
7
7
?4d":wd': 5466  	D+SS1E1O-P-P	Q	QRRZ\\WYY 
 *$// =0 O+"66 O+		8	$	$" " " " " " ">1T 1 1 1 1h, , , , , ,` ! ""
j))
" 
s:ch&&J(((MMM 
IN	
E   	4$;   # #* # # #d s    d    ,
4$ 
4 
4 
4 
4 (1 #-  D    ., ,TD[ ,C , , , ,,+ + + +D      
 
4 
D 
 
 
 
  sTzD0 DJ    >      3:#4     	'T 	' 	' 	' 	' 0/11 	6;;!
 !
 !
 !
H0u 0 0 0 0(  =!=rBB  POO

(
(
(  	6
K
L \ K	
 O \ N K   [ K & P    V!" L#$ ;  B   * 1	 
 
 

g 
g 
g 
g %$&&.$9Sabb. $4S\]].
 $?Sdee. $?Sdee. $ASfgg. $ASfgg. $@Seff. $CYqrr. $CYqrr. $JYxyy. $;Yijj. $=Ykll." $@Tghh#.$ $>Teff%.& $DTjkk'.( $:Tjkk)., $6S`aa-.. $7Sabb/.2 $4R]^^3.6 $DSbcc7.: $:Q_``;.< $DQijj=.@ MQZ[[A.B KQXYYC.D $5Q^__E.F MQZ[[G.H MQZ[[I.J $7Q`aaK.R %@]tuuS.T %;]oppU.V %?]qrrW.X %M]vwwY.Z %J]{||[. b
M,  h	
 N !  , 
< $ 
 y # h ,  =!" y#$ "" 9   X+	5+
E+ E+ U	+
 i+ i+ Y+ I+ h+ X+ + M+ + k+ ;+  !+" \#+ +$ ,%+& '+( E)+* E++, E-+. 
9/+0 91+2 i3+4 i5+6 I7+8 i9+: ;+< Y=+> 
8?+@ (A+B HC+D E+ +F  U+ + + \,# ,# , , , ,,& S     C C CT
 C C C C TD[ CH     # t 	   ( #* * * **t* 	*
 * 	* * * *Z'F 's ' ' ' 'T
( 
(C 
( 
( 
( 
(>v >#c( > > > >0 /2cee !3s8 3 3 3"6 "c " " " " C    F sTz    0 # t 	    F +<== +<=="-@AA"-@AA!,>??F 	227777	22	F 9--779--779999"-@AA#.BCC"-BCC
F2 '=UVV'=UVV.=\]]=MNN!=OPP3F@ "-@AA +<==&1GHH"-IJJ	AFL 0?[\\2?]^^*?XYY4?bcc	MFX 
9--))}559--9--99YFh *:;; +<==!,>??}55[11iFv 77'2JKK77'2JKK77wFD 777777l33	EFR 9--779--77(++ +<=="-@AA'2JKK	SFh $~>>"\::)44l33	iFv  %}55%9--% 77% 77	%
 99% %/DEE% 9--% 99% 9--% 99% #-@AA% $.BCC% ))% }55% l33%  !+<==!%" !+<==#%$ !+<==%%& !+<=='%( #-@AA)%* #-@AA+%, !+<==-%. ",>??/%0 ",>??1%2 (2JKK3%4 (2JKK5%6 /9XYY7%8  *:;;9%: ",>??;%< 9--=%> ))?%@ [11A%B 77C%D #-@AAE%F '1HIIG%H l33I%wFD I66G44K88K88 ,=>>",?@@M::N;;O<<K88N;;N;;N;;N;;EFj '=UVV'=UVV.=\]]=MNN!=OPPkFz %@@%@@{FD 7788EFP 3=TUU/:OPP9Dbcc1<LMM	 O<<K88M::N;;O<< [11 [11 2Lnoo4Lnoo3Lpqq?LnooALfgg@Lopp}F F F R $)X$?@@ 	S 	 	# 	$ 	 	 	 	c c    4 C  C        L     %)'	\ \ \3i\ Tz\ 	\
 49d3i \ \ \ \~T
 4Z 
$Z	   .?D4: ?D$ ?D ?D ?D ?Dl   3 4    s t    ># # # # # #L  6^6S ^6U ^6 ^6 ^6 ^6B_C _E#*cTXjBX<Y _ _ _ _J &I|]&CDD &" &"# &"sTz &"UX &" &" &" &"R TD[ C    2 H   $     3 S T#Y    B  "B BDjBtB DjB 
#Y	B B B BN  "	  Dj t Dj	
 
   @" " " " " "(" " " " " "8<2s <2t <2 <2 <2 <2F
#c3h 
 
 
&d & & & &R1Lc 1LS 1L 1L 1L 1L 1L 1Lj (,  + + +$' E ' ' ':> *D4K > > >%, U , , ,.y00 %)%&BCC   EG S%/?(?"@@A F F F46 c5j!1 6 6 6d
    D  !  !44 2d 2 2 2 2 $    $4    h #,) - - - # # 2  $.4 .D . . . .b$    $   & T    .16 1d 1 1 1 1hdTk    >#d #t # # # #L $+    4# # #>W# W W W W*## # # # #43  #    B)s )tCy ) ) ) )X tCy TRVZ    &T#Y & & & &RW%d W% W% W% W%v, x',6  y~IN	2 2 2 2 2 2 2 2j}        y~d       T     t      !  ! ! ! T    d   !$ 3 & & &
 T   !9>## %) edl ) ) )DIKK 
'3 
't 
' 
' 
' 
'# #T # # # #+S +T + + + +    /F{/F/H/H [, H H H )9>++  fS  fT  f  f  f  fF io    
  T   )9>++ / /	 / / / /8).//)E) ) 	)
 u) #E) ) ) ) ') ) ) E)  ) V)  I!)" #) )$ E%)& e')( Q))* "1+), "2-). $R/)0 &u1)2 $W3)4 25)6 7)8 		 9)> U?)@ 
5A)B UC)D TE)F tG)H $I) )J ! #Q) ) ) T VUU 444     %#  #!%S/ #! #! #! #!L#t # # # #L /446677;  
 ,'!:.888 6 6 6666   %#*#+%-'5%6     . Jt$$,,-TUU ]D ]T ] ] ] ]J "MOO ")0022 " " "!" 29566 
55!!"566
 
 /4000011SS9J5K5KKK145F1G1G-.	$$
,5KKK  %      	 	 	D	 %<K$;$=$=+
! = = =	////// 	 	 	DD	sB   ~ ~%$~%@.,AA AAA#A"AA#BAB BABBAB