
    }-ji                    z   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mZ ddlmZmZ  ej                    Z ee          j        j                                        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 ej        d e edz  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fdZ,dede fdZ- e,            Z. e-e.          Z/e.8 e e.          ej0        vr#ej0        1                     e e.                     dZ2ndZ2i Z3 e	j4                    Z5da6e7e8d<   da9edz  e8d<   da:e dz  e8d<   de;de fd Z<de=fd!Z>defd"Z?d#d$hZ@d#ZAde;fd%ZBd6d&e;dz  de fd'ZCd7d(ZDd)ede;fd*ZEd)ed&e;ddfd+ZF eD             e3ZGd6d,e ez  dz  deHe         fd-ZId.ede=fd/ZJd6d,e ez  dz  defd0ZKdefd1ZL eL            aM ej        d2d
          ZNd7d3ZOdePfd4ZQd5ZRd6ZSh d7ZTh d8ZUh d9ZVi d:d;d<d=d>d=d?d@dAdBdCdDdEdFdGdHdIdJdKdLdMdNdOdNdPdQdRdSdTdUdVdWdXdYdZd[d\d]d]d^d_d`dad`dbdcddZWg deZXd6dfZY eY            ZZg dgdhdidjdgdkdldjdmdndodjdmdpdqdjdmdrdsdjdmdtdudjdmdvdwdjdxdydzdjdxd{d|djdxd}d~djdxdddjdxdddjddddjddddjddddjddddjddddjddddjddddjddddjddddjddddjddddjddddjddddjddddjddddjddddjddddjddddjddddjddddjddddjZ[i ddddddmddgddddddddddddddēddxddǓddɓdd˓dd͓ddϓddddddddd՜Z\i 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ʓddʓddddddddddddZ]de de fd Z^de_de fdZ`d6de;dz  deHe;         fdZad6de;dz  debe          fdZc	 d6de_de;dz  de fdZd	 d6dddde_de;dz  de_d	e=de f
d
Zede_de fdZfde_de fdZg	 d6de_de;dz  de fdZhi dddodddqdddsdddudddwdgdddddddddidddldgdddddddddldddiddddddddd dd!d"dd#d$dg	dŐd%dzd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gdd/d0dd1d2dd3d4dd5d6dgdd7ddd8ddd9ddd:ddd;ddd<ddgdd=d>dd?d@ddAdBddCdDddEdFdgddGdddHdddIdJddKdLddMdNdgdÐdGdddIdJddMdNddOdPdgdddddddddldddiddQdRdddqdddsdd&d|dgdg dSdTdddldddiddUdVdddddWdXddYdZddddd[d\dd]d^ddd dd!d"dd_d`ddadbddcddddedoddfdqddgdhddidjddkdsddduddldmdddwddndodd%dzdd&d|dd'd~dd(ddd)ddd7ddd8dddEdFddpdJddqdrddsdtddudvddwd7ddd8dddEdFddxdydd+ddd*dddzd{dd|d}dd~dddddddddpdJdddddddgdސd%dzdd&d|dd'd~dd(ddd)d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dgd~ddddddzd{dd|d}ddddgdddgdZi ejddh          Zkde de de de=fdZlde de fdZmde de fdZndZodZpdZqdepddeHe          de dz  dedePeHe          eHe          f         fdZrdeHe;         de de dz  deHe;         fdZsdeHe;         ddfdZth dZude de=fdZvde de=fdZwde de=fdZxd Zyde dePfdZzde dePe dz  e dz  f         fdZ{d6de de dz  de fdZ|d6d&e;dz  de fdZ}dZ~d Zde;fdZde=de;fdZde de;fdZde de;fdZdae;dz  e8d<   dae7e8d<   dae;dz  e8d<   dZe7e8d<    e	j                    Z e	j        e          adai ae;e ePe7df         f         e8d<   i Ze;e e7f         e8d<   de dz  fdʄZdZe"dz  Zdefd̈́Zd.ede;fd΄Zde;fdτZd7dЄZde_de=fd҄Zde_de=fdӄZde;dz  fdԄZde;ddfdՄZde7de;dz  fdׄZd؄ Zde fdلZde fdڄZde deHde fd܄Zde deHe          fd݄Zde deHe          deHe;         fdބZdeHe          fd߄Zde;fdZedz  dz  Z e	j4                    ZdZ e	j4                    Z G d d          ZdefdZi Ze;e8d<    e	j4                    Zi Ze;e8d<   i Ze;e8d<   i Ze;e8d<   i Ze;e8d<   i Ze;e8d<   i Ze;e8d<    eb            Zebe8d<   i Ze;e8d<    e	j4                    Zdae7dz  e8d<    e
j
                    Zde ddfdZde ddfdZde ddfdZddlZ ej                    Zej        e8d<   dZ e	j4                    Zde ddfdZ e	j                    Zd Zd Zi Ze;e8d<    e	j4                    ZÐde de	j4        fdZi d e t                    dddd dddddddddddddd	d
d	ddddddd ej        dd          ddddddddddddZh dZh dZh dZȐddddddZdePe e f         fd Zde;fd!Z ebeŠ                                          d"d#hz
  Z͐d d$hdd%hh d&h d'h d(d)Zh d*Z eАd+                              d,          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 eKeԠ                    d                    aMeԠ                    d#d           eԠ                    d           e t                    k    rM e t                    eԐd<   	 e&                     ej        edd01          d23           n# e$ r Y nw xY w ej                    Zej        e8d4<   	 dd5lmZ  eߦ             dS # e$ r Y dS w xY w(8  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HERMES_WEBUI_HOST	127.0.0.1HERMES_WEBUI_PORT8787HERMES_WEBUI_TLS_CERT HERMES_WEBUI_TLS_KEYHERMES_WEBUI_STATE_DIR.hermeswebuisessionszworkspaces.jsonz_index.jsonzsettings.jsonzlast_workspace.txtzprojects.jsonreturnc            	         g } t          j        d          rX|                     t          t          j        d                                                                                               t          j        dt          t          dz                      }|                     t          |                                          dz             |                     t          j	        dz             t          j	        dz  
                                r|                     t          j	                   |                     t          dz  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_HOMEr   zhermes-agentzrun_agent.pyXDG_DATA_HOMEz.localshare)z/optz
/usr/localz/usr/local/shareN)osgetenvappendr   
expanduserresolvestrHOME	REPO_ROOTparentexists)
candidateshermes_homexdg_data
sys_prefixpaths         /root/hermes-webui/api/config.py_discover_agent_dirr'   ;   s    J 
y)** 
34455@@BBJJLL	
 	
 	

 )M3ti/?+@+@AAKd;''2244~EFFF i&7888 	>)1133 ,)*+++ dY&7888 d^+,,, BIos4(?W3L/M/MNNOOHh))++n<=== A = =
$z**^;<<<< " ";;== 	"d^3;;== 	"<<>>!!!4    	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)python3r.   r0   )r   r   r    r   r   shutilwhich)r)   venv_pyvenv_py_win
local_venvr1   namefounds          r&   _discover_pythonr8   o   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dumpsr   	Exceptionrepr)r=   s    r&   _fingerprint_configrH      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)r<   rH   
_cfg_cachecfg	NameError r(   r&   _cfg_has_in_memory_overridesrN      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  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.yamlr   )r   r   r   r   api.profilesrR   ImportErrorr   )env_overriderR   s     r&   _get_config_pathrV      s    9122L /L!!,,...0777777%%''-77 0 0 0i-////0s   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.r9   )rV   statst_mtimeOSErrorr:   r;   rJ   rN   reload_configrK   rL   )config_pathcurrent_mtimecache_stales      r&   
get_configra      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dictrK   get _DEFAULT_WEBUI_SESSION_SAVE_MODEr   striplower_WEBUI_SESSION_SAVE_MODES)rb   
active_cfg	webui_cfgmode
normalizeds        r&   get_webui_session_save_moderp     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.r9   r   Nutf-8encodingz"Failed to load yaml config from %s)	_cfg_lockrJ   clearrV   r:   r;   yamlr    	safe_load	read_textre   rf   updater   rZ   r[   r\   rF   loggerdebugrH   r<   _delete_models_cache_on_disk)r^   _old_cfg_mtime_yamlloadeds       r&   r]   r]   #  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r^   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   rr   rs   z#Failed to parse yaml config from %s)
rw   rT   r    rx   ry   re   rf   rF   r{   r|   )r^   r   r   s      r&   _load_yaml_config_filer   E  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)rA   allow_unicoderr   rs   )rw   rT   RuntimeErrorr   mkdir
write_text	safe_dump)r^   rb   r   excs       r&   _save_yaml_config_filer   U  s    V V V VKLLRUUV TD999uDII      s    
&!&rawc                    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   r   r   rF   r   )r   r%   r!   s     r&   addz"_workspace_candidates.<locals>.addl  s    
""F		??--//7799DD 	 	 	FF	z!!d##### "!s   3= 
A
AHERMES_WEBUI_DEFAULT_WORKSPACE	workspacework)r   r   r   r   r   r    	STATE_DIR)r   r   home_workspace	home_workr!   s       @r&   _workspace_candidatesr   h  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)
r   r   r   is_dirr   accessR_OKW_OKX_OKrF   )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   rM   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   : r@   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)r   
_AGENT_DIR
PYTHON_EXEr   DEFAULT_WORKSPACEHOSTPORTrV   r    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__rF   r   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@ i  @>   .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webhookc                     | t                      } 	 ddlm} t           || d                    S # t          $ r2 |                     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)ra   hermes_cli.tools_configr   listrF   rg   _DEFAULT_TOOLSETS)rK   r   s     r&   _resolve_cli_toolsetsr   9  s     {llN??????''U33444 N N Nww*B//33E;LMMMMMNs   1 9A-,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copilotzGitHub Copilotzaiz
Z.AI / GLMkimi-codingzKimi / Moonshotdeepseekminimax
minimax-cnzMiniMax (China)googlez
meta-llamaz
Meta LlamahuggingfaceHuggingFacealibabaAlibabaollamaOllamaollama-cloudzOllama CloudzOpenCode ZenzOpenCode Goz	LM Studioz
NVIDIA NIMXiaomi)opencode-zenopencode-golmstudio	mistralaiqwenx-ainvidiaxiaomiglmz-aizz.aizhipugithubz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awsbedrockzaws-bedrockamazonzamazon-bedrockr!  aliyun	dashscopezalibaba-cloudnimr#  z
nvidia-nimzbuild-nvidianemotronr$  custom)mimozxiaomi-mimolocalr6   c                     | 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)r   ri   rj   hermes_cli.modelsr<  rF   rg   )r6   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,})r   ri   rj   
startswithresub)r6   r   slugs      r&   _custom_provider_slug_from_namerG    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 rM   )re   rf   .0entrys     r&   
<listcomp>z,_custom_provider_entries.<locals>.<listcomp>  s'    BBBe*UD*A*ABEBBBr(   )re   rf   rK   rg   r   )rH  sourceentriess      r&   _custom_provider_entriesrR    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 rM   rM   )rM  rF  s     r&   	<setcomp>z/_named_custom_provider_slugs.<locals>.<setcomp>  s0          r(   c              3   Z   K   | ]&}t          |                    d                     V  'dS )r6   N)rG  rg   rL  s     r&   	<genexpr>z/_named_custom_provider_slugs.<locals>.<genexpr>  sJ       
 
 ,EIIf,=,=>>
 
 
 
 
 
r(   )rR  )rH  s    r&   _named_custom_provider_slugsrX    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   rA  r6   )r   ri   rj   removeprefixrR  rg   rG  )r   rH  r   
raw_suffixrN  
entry_namerF  s          r&   (_named_custom_provider_slug_for_providerr]    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   r8  )r]  r   ri   rj   (_named_custom_provider_slug_for_base_urlr?  )r   rH  r_  r`  
named_slugr   by_base_urlresolveds           r&   _resolve_configured_provider_idrf    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   _rB  )r   ri   rj   replace_PROVIDER_DISPLAY_PROVIDER_MODELSr?  )r6   r   re  s      r&   _canonicalise_provider_idrl  K  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)r   ri   rstripr   schemerj   netlocr%   )rm  url
parsed_urlrt  ru  r%   s         r&   _normalize_base_url_for_matchrx  u  s    
ekr


 
 
"
"
)
)#
.
.C r###?S??CCJ)60022F2:?99;;BB3GGF?!!#&&D ''''''r(   c                     t          |           }|sdS t          |          D ]P}t          |                    d                    }||k    r+t          |                    d                    pdc S dS )Nr   r_  r6   r8  )rx  rR  rg   rG  )r_  rH  targetrN  entry_base_urls        r&   rb  rb    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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 Codexz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.20)r$  r"  gh_cligh auth tokenrP  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~  )ri   rj   _AMBIENT_GH_CLI_MARKERS)rP  r   r  s      r&   _is_ambient_gh_cli_entryr  J  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)'
    r@   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 )	NrB   rh  .r      r      )	ri  splitisalphar   r   upper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(    ())	partitionr   )r  	name_partrh  variantr  r   s         r&   _format_ollama_labelr  V  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)
    ro  r  r  r  Nz (via Nous))r  rj   rC  r   r  )r  r  bases      r&   _format_nous_labelr  p  s    , *-		#q!!"%%I ##I.. ;	#i..// ::		**Dr(         )r  r  r  
moonshotair&  r  r!  r"  r  stepfunr$  tencentr#  zarcee-ai)selected_model_idrz  live_idsr  rz  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)r   r   )r  chosen
chosen_sets    r&   _addz&_build_nous_featured_set.<locals>._add  sO     	 3j((MM#NN3	  	 ((r(   @nous:r	  r   r   ro  r  r   c              3   >   K   | ]}|t                    v|V  d S r  )set)rM  vprioritys     r&   rW  z+_build_nous_featured_set.<locals>.<genexpr>  s3      EEAas8}}.D.Da.D.D.D.DEEr(   c                     g | ]}|v|	S rM   rM   )rM  mr  s     r&   rO  z,_build_nous_featured_set.<locals>.<listcomp>  s#    999AQj%8%8a%8%8%8r(   )r   _NOUS_FEATURED_THRESHOLDr   r  r   rC  rk  rg   r  
setdefaultr   _NOUS_VENDOR_PRIORITYsortedpop)r  r  rz  r  selstaticsid	by_vendorr  vendorleftovervendor_orderadded_this_passbucketextrasr  r  r  s                  @@@r&   _build_nous_featured_setr    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provider_id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   @ro  r   r|  r@   )rj   r   rC  r   )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   rg   )ir  s    r&   <lambda>z(_deduplicate_model_ids.<locals>.<lambda>>  s    fQimmM266 r(   keymodelsr   r   r     r  r  r@   r   r   r  r  )r  ranger   	enumeraterg   r   ri   rC  r  r   items)r  sorted_group_indicesid_mapgigroup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tabbyr  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   TrA  F)r   ri   rj   _LOCAL_SERVER_PROVIDERSrC  rZ  )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   	ipaddresshostnamerj   
ip_address
ValueErroris_loopback
is_privateis_link_localrF   )r_  r   r  host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   r@   Fr  i  r   NTr  r  )	r   ri   rsplitr  intr  r  r  rj   )r  r  port_sport_nr  hls         r&   &_custom_slug_rest_looks_like_host_portr    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   ro  r  r   N)rK   rg   ri   rs  re   rf   r   rj   )r  prov_cfgexplicit	model_cfgmodel_provider
model_bases         r&   _get_provider_base_urlr    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}|o	|duo| |k    }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          rd| v r| 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          \  }}||t-          |          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 |r6t1          |          st3          |          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^  r:  r8  r   rA  rC   rJ  r6   r  c              3      K   | ]A}t          |t                    |                                +|                                V  Bd S r  )re   r   ri   )rM  r  s     r&   rW  z)resolve_model_provider.<locals>.<genexpr>=  s`       ' '!#s++' 14		'IIKK' ' ' ' ' 'r(   r  r@   r  r  ro  r
  >   r	  r#  r  r  r  z%https://chatgpt.com/backend-api/codex)rK   rg   re   rf   rf  r   ri   rj   rC  r   r  r   rz   keysrG  r  countr   r  rk  rj  r  r  rs  r  r  )r  config_providerconfig_base_urlr   _is_explicit_non_custom_provider_default_model_skip_custom_providersrJ  rN  entry_modelr\  r{  entry_model_idsentry_modelsprovider_hintinner
bare_model
_slug_restextraprefixbare_PORTAL_PROVIDERSs                         r&   resolve_model_providerr!    sy   * 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( 	'$&	'& 
 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:=*@*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//  	> */:: 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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                    rr|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                    r2 ||                    d          |                    d                    }|sGt          |t                    r2 ||                    d          |                    d                    }|sNt          |t                    r9|d||hv r2 ||                    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   rA  )NNrm  r   c                 "   t          | pd                                                                                              dd                              dd          }d|v r|                    dd          }d|v |                    d          S )Nr   rh  rB  r  z--)r   ri   rj   ri  )rm  r  s     r&   _slugifyz4resolve_custom_provider_connection.<locals>._slugify  s    ""$$**,,44S#>>FFsCPPaii		$$$A aiiwws||r(   r@   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 )Nz${}r  r  r  r   )r   ri   rC  endswithr   r   r   )raw_api_keyraw_key_envapi_key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r(   rJ  r6   r_  r*  r,  r   r  r8  r  r   )r   ri   rj   rC  r  ra   rg   re   r   rf   r   )r  r  r$  rF  cfg_datar-  rJ  rN  r6   
entry_slugr_  r*  providers_cfgprovider_specificprovider_customr  r	  fallback_baser   _basefallback_keys                        r&   "resolve_custom_provider_connectionr6    s    kR
 
 
&
&
(
(
.
.
0
0C>>)$$ z      8CIIc1%%a(..0011D z ||H#*      ||$6;;&-- ! ! !%&& 	599V$$*++1133 	Xd^^
uyy,,23399;;Ct,uyy33UYYy5I5IJJ     !!j1A!1Dd&K&K! #L9--uyy/C/CDD		*%%+,,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** h#|$5$9$9)$D$DFWF[F[\eFfFfgg dJ== d#|O$7$7	$B$BODWDWXaDbDbcc XJy$77 XNxY\^bNc<c<c#|IMM)$<$<immI>V>VWW 3} 3]2d22:r(   r	  c                 0   t          | pd                                          }t          |pd                                                                          }|r|r|dk    s|                    d          r|S t                              di           }d}t          |t                    rHt          |                    d          pd                                                                          }||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   rC   r  r  Nr   r
  r@   ro  )r   ri   rj   rC  rK   rg   re   rf   )r  r	  r  r   r  r  s         r&   model_with_provider_contextr8    s8    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%%% 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  rC   r   HERMES_MODELOPENAI_MODEL	LLM_MODEL)	rK   DEFAULT_MODELrg   re   r   ri   rf   r   r   )rb   rl   default_modelr  cfg_default	env_models         r&   get_effective_default_modelrA  /  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)rI  effort)r   ri   rj   VALID_REASONING_EFFORTS)rJ  effs     r&   parse_reasoning_effortrM  K  s      V**,, t
f++




#
#
%
%C
f}}5!!
%%%3///4r(   c                     t          t                                } |                     d          pi }|                     d          pi }t          |t                    r|                    d          nd}t          |t                    r|                    d          nd}t          |t
                    rt          |          ndt          |pd                                                                          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_effortTr   )rQ  rR  )	r   rV   rg   re   rf   boolr   ri   rj   )rb   display_cfg	agent_cfgshow_raw
effort_raws        r&   get_reasoning_statusrX  ]  s     ))9););<<K//),,2K((.BI4>{D4Q4Q[{/000W[H6@D6Q6Q[1222W[J -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.
    rO  rQ  N)
rV   ru   r   rg   re   rf   rS  r   r]   rX  )rY  r^   rb   rT  s       r&   set_reasoning_displayr[  q  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	rJ  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 requiredrH  zUnknown reasoning effort 'z'. Valid: none, z, r  rP  rR  N)r   ri   rj   r  rK  r   rV   ru   r   rg   re   rf   r   r]   rX  )rJ  r   r^   rb   rU  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   r:  r8  rC   ro  r_  r  zhttps://api.openai.com/v1rA  NT)r   r  )r   ri   r  rV   ru   r   rg   re   rf   r!  rj   rs  rC  r  r   r]   invalidate_models_cache)r  selected_modelr^   rb   r  previous_providerresolved_modelresolved_providerresolved_base_urlpersisted_modelpersisted_providers              r&   set_hermes_default_modelrg    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_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modulesrg   getattrr   rF   )_sysr   r  s      r&   _current_webui_versionrv    sz    	l}--;4C$//$s1vvv$   tts    A #A 
AAr  models_cache.jsonc                  d    	 ddl m}   |             dz  S # t          $ r t          dz  dz  cY S w xY w)z8Return the auth.json path for the active Hermes profile.r   rQ   z	auth.jsonr   )rS   rR   rT   r   )_gahs    r&   _get_auth_store_pathrz  "  s[    .??????tvv## . . .i+----.s    //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)r   r   r   rZ   r\   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                  p    t          t                                t          t                                dS )zGReturn the current config/auth-store fingerprint for /api/models cache.)config_yaml	auth_json)r  rV   rz  rM   r(   r&    _models_cache_source_fingerprintr  >  s7     66F6H6HII34H4J4JKK  r(   c                  v    	 t          j        t          t                               d S # t          $ r Y d S w xY wr  )r   unlinkr   _models_cache_pathr\   rM   r(   r&   r}   r}   F  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  r>  r  configured_model_badgesr  Nr>  r  r  )re   rf   issubsetrg   r   r   )r  r  s     r&   _is_valid_models_cacher  M  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)
r  re   rf   rg   _MODELS_CACHE_SCHEMA_VERSIONr{   r|   rv  r   r  )r  cached_schemaruntime_versioncached_versioncached_sourcesruntime_sourcess         r&   _is_loadable_disk_cacher  d  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   Nrr   rs   r  r>  r  r  r  r>  r  r  )rD   r  r    openloadr  rF   )_jfr  s      r&   _load_models_cache_from_diskr    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  r>  r  r  )r  r  r  r>  r  r  r  r  z.tmpwrr   rs   r  )indent)r  r  r  rv  r   r  r   getpidr  rD   dumprenamerF   )r  payloadr  tmpr  s        r&   _save_models_cache_to_diskr    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=%rr9   )
rh  ri  rl  r  rj  r{   r|   r  copydeepcopy)r  current_sourcess     r&   _get_fresh_memory_models_cacher    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.
    Nr9   F)
_available_models_cache_lockrh  ri  rj  _cache_build_in_progress_cache_build_cv
notify_allrn  rv   r}   rM   r(   r&   r_  r_    s     
& 
' 
'"&%("592#( ""$$$
 	$$&&&
' 
' 
' 
' 
' 
' 
' 
' 
' 
' 
' 
' 
' 
' 
' !"""""s   ;AAAc                     t           5  t                              | d           t                              t          |           d           ddd           dS # 1 swxY w Y   dS )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.
    N)r  rn  r  r?  r  s    r&    invalidate_credential_pool_cacher  	  s     
& 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 O O Os   AAA A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')
    Nr9   )
r  rh  ri  rj  timero  rn  r  r?  r}   r  s    r&    invalidate_provider_models_cacher  "	  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  r@   r  c                     d| v r|                      dd          d         n|                     dd                                          S )Nro  r  r  rB  r  )r  ri  rj   )r  s    r&   r  z&_get_label_for_model.<locals>.<lambda>M	  s@    cQhhqwwsAr**AFFsCPPVVXX r(   r  r   r   r   ro  r  r  c              3     K   | ]{}t          |          d k    rP|                    dd                                          r(|                                s|                                n|                                V  |dS )r  r  r   N)r   ri  r  r  r  
capitalize)rM  r  s     r&   rW  z'_get_label_for_model.<locals>.<genexpr>W	  s         !ffkkaiiR&8&8&@&@&B&Bk199;;k			]^]i]i]k]k     r(   rh  rB  )rC  r  rg   r   r   ri  )r  r  	lookup_id_normnorm_lookupgr  r  s           r&   _get_label_for_modelr  <	  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)r   ri   r=  r  rF   r?  r   r  r{   r|   r   )r  r  _provider_model_idsr!   aliasseenr   r  r  r  mid_ss              r&   _read_live_provider_model_idsr  ]	  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.r  r  Nr   r|  )r  r  r   ri   r   r  r   )r  r  	formatterr  r  r  r  r   s           r&   _models_from_live_provider_idsr  	  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.
    
CODEX_HOMEr   z.codexrw  rr   rs   r  NrF  
visibility)hidehiddenr  i'  c                 "    | d         | d         fS )Nr   r  rM   )items    r&   r  z5_read_visible_codex_cache_model_ids.<locals>.<lambda>	  s    DGT!W#5 r(   r  )r   r   r   ri   r   r   rD   loadsry   rF   re   rf   rg   r   r   rj   r  floatr   sort)
codex_home
cache_pathr  rQ  sortabler  rF  r  r  rankorderedrh  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}]}]
    }
    r9   r   c            	      .E   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  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|                    d0           |                    d1          r*|                    d2           |                    d3           |                    d4          r|                    d5           |                    d6          r|                    d7           |                    d8          r|                    d9           |                    d:          r|                    d;           |                    d<          r|                    d=           |                    d>          r|                    d?           |                    d@          r|                    dA           |                    dB          r|                    dC           |                    dD          r|                    dE           |                    dF          r|                    dG           |                    dH          r|                    dI           |                    dJ          r|                    dK           |                    dL          r|                    dM           |                    dN          r*|                    dO          r|                    dP           t                              dQi           } t          | t                    r=| D ]:}!tW          |!          }"|"s|"tX          v s|"| v s|!| v r|                    |"           ;d	tZ          dt          ffdR}#g }$i }%|r	 dd l.}&dd l/}'|&                                }(|(0                    dS          r|(dTz   })n|(1                    dU          dVz   }) |#|(          }*|*pdW}+te          |*          },tg          dX|(v r|(ndY|(           }-|-j4        p|-j5        6                                }.|-j7        r_|,s]	 |&8                    |-j7                  }/|/j9        s|/j:        s|/j;        rdZ|.v sd[|.v sd\|.v rdZ}+ndP|.v sd]|.v rdP}+ndW}+n# tx          $ r Y nw xY wi }0d}1t          t                    r)                    d^          pd&                                }1|1st                              dQi           }2t          |2t                    rmt{          d dWg          D ]Z}3|2                    |3i           }4t          |4t                    r-|4                    d^          pd&                                }1|1r n[|1sHd_}5|5D ]C}6|                    |6          ptS          j*        |6          pd&                                }1|1r nD|1rd`|1 |0da<   dd l>}7t                      }8|rKtg          dX|v r|ndY|           }9|9j7        r,|8                    |9j7        6                                           t                              dbg           }:t          |:t&                    r|:D ]};t          |;t                    s|;                    d	          pd&                                }<|<rKtg          dX|<v r|<ndY|<           }=|=j7        r,|8                    |=j7        6                                           tg          dX|)v r|)ndY|)           j?        dcvrty          ddj?                   j7        r	 |7@                    j7        d           }>|>D ]\  }?}?}?}?}/|&8                    |/d                   }@|@j9        s|@j:        s|@j;        rRt;          fdedfD                       pj7        pd6                                |8v }A|Asty          dg|/d                    n# |7jA        $ r Y nw xY w|'jB        C                    |)dhi          }B|BD                    djdk           |0                                D ]\  }}|BD                    ||           |'jB        E                    |Bdlm          5 }Ct          j
        |CF                                G                    d                    }Dd d d            n# 1 swxY w Y   g }Edn|Dv r$t          |Ddn         t&                    r	|Ddn         }En'do|Dv r#t          |Ddo         t&                    r|Ddo         }E|ED ]}Ft          |Ft                    s|F                    dd          p+|F                    dpd          p|F                    dd          }G|F                    dpd          p|F                    dd          p|G}H|Gr|Hr|+dqv rt          |G          n|H}I|G|Idr}J|$I                    |J           |+6                                }3|%J                    |3g           I                    |J           |                    |3           n+# t          $ r t                              ds|+           Y nw xY wt                              dbg           }:i }Kt          |:t&                    rt                      }L|:D ]};t          |;t                    s|;                    dp          pd&                                }M|Mrt          |M          nd }N|Nr|N|Kvr|Mg f|K|N<   g }O|;                    dd          }P|Pr|OI                    |P           |;                    do          }Qt          |Qt                    rY|QD ]V}Rt          |Rt                    r?|R&                                r+|R|Ovr'|OI                    |R&                                           W|OD ]}P|Nr|N dt|P n|P}S|Pr|S|Lvrt          |Pg           }T|L                    |S           |Nr_|                    |N           |P}U|Nk    r|U'                    du          sdu|N dt|U }U|K|N         d*         I                    |U|Tdr           |$I                    |P|Tdr           |                    dW           ǐt          |:t&                    ot9          |:          dk    }Vr\dWk    rV|VsT|M                    dW           t'          |          D ].}N|N'                    dv          r|Vs|M                    |N           /n8dWk    r2|Vr0t;          dw |:D                       }W|Ws|M                    dW           t          t                    }Xt          |t                    }Y|Yrw|Xrut'          |          D ]e}	t          |	pd          &                                6                                }Z|Z'                    dv          r|Z|Xvr|M                    |	           ft                              dQi           }2t          |2t                    r|2                    dxd          nd}[|[rt                      }\r|\                               t                              dQi           }]t          |]t                    r1|\P                    dy |]                                D                        |Q                    |\          }|r;t                      }^|D ](}	tW          |	          p|	}_|^                    |_           )|^}|rt          |          D ]}`|`'                    dv          rC|`|Kv r>|K|`         \  }a}b|bs|%                    |`g           }b|brI                    |a|`|bdz           [t                              |`|`T                                          }c|`d5k    r)g }dt                      }e	 dd{lmU}f  |f            pg }g|gD ]8\  }h}i|hr1|h|evr-|e                    |h           |dI                    |h|hdr           9n*# t          $ r t          V                    d|           Y nw xY w	 dd l/mB}j |jC                    d}d~di          }k|jE                    |kdm          5 }lt          j
        |lF                                G                                          }md d d            n# 1 swxY w Y   d}nd}o|m                    dng           pg D ]}pt          |pt                    st          |p                    d          pd          &                                }q|qr|q|ev rV|p                    d          pi }r	 t          |r                    dd          pd          dk    o(t          |r                    dd          pd          dk    }sn# t          tx          f$ r d}sY nw xY w|sp|q0                    d          }s|sst          |p                    dp          pd          &                                p|q}tdU|tv r|t(                    dU          d         n|t}ud|u6                                vr|u d}u|e                    |q           |dI                    |q|udr           |nd*z  }n|n|ok    r nn*# t          $ r t                              d           Y nw xY w|dsd t          D             }dI                    dd5|ddz           |`dk    ryg }d	 ddlmZ}v d  |vd          pg D             }dn*# t          $ r t          V                    d           Y nw xY w|dr*t          |d|`          }wI                    |c|`|wdz           7|`d3k    rg }dg }x	 ddlmZ}v d  |vd3          pg D             }xn*# t          $ r t          V                    d           Y nw xY wt                      D ]}h|h|xvr|xI                    |h           d |xD             }d|ds-t          j^        tX                              d3g                     }d|dr*t          |d|`          }wI                    |c|`|wdz           |`d!k    rg }dg }yd}zd}{	 ddlmZ}v  |vd!          pg }|n.# t          $ r! t          V                    d           g }|d }{Y nw xY w||rt                              di           }}t          |}t                    r|}                    d          ppd }~t          |||~          \  }}d |D             }dd |D             }y|r#dt9          |           dt9          ||           d}znJ|{st          V                    d           n-t          j^        tX                              d!g                     }d|drKt          |d|`          }w|yrt          |y|`          ng }|c|zz   |`|wdz}|r||d<   I                    |           |`dPk    rqg }dg }	 ddlmZ}v  |vdP          pg }n*# t          $ r t                              d           Y nw xY w|rd |D             }dnt                              dQi                               dPi           pi }t          dP          pd}t          |t                    r6t          |                    d^          pd          &                                nd}|r^djdki}0|rd`| |0da<   |dTz   1                    dU          }	 dd l/mB}j |jC                    |dh|0          }B|jE                    |Bdm          5 }t          j
        |F                                G                                          }d d d            n# 1 swxY w Y   |                    dn          pg D ]n}t          |t                    rWt          |                    d          pd          &                                }h|hr|h|hdr|dvr|dI                    |h|hdr           on+# t          $ r t                              d|           Y nw xY w|dr*t          |d|`          }wI                    |c|`|wdz           	|`tX          v s|`t                              dQi           v rOt                              dQi                               |`i           }4g }dt          |4t                    rado|4v r]|4do         }t          |t                    rd |                                D             }dn!t          |t&                    rd |D             }d|dst          |`t          |`                    }d|ds-t          j^        tX                              |`g                     }d|%                    |`g           }|r|dst          j^        |          }dt          |d|`          }wI                    |c|`|wdz           
|%                    |`          }|rt          j^        |          }n*|$r&|`dWk    rr	dWk    rg }nt          j^        |$          }ng }|rI                    |c|`|dz           n/r-t                    }II                    dd|Idrgdz           r[t                    &                                6                                c                    dd          t          v ptW                    t          v }|rt          V                    d           nوfdD             }           |vrt                    }Ir/t                              pd          6                                nd}d}D ]S}|rO|                    dd          6                                |k    r#|do         d                    d|Idr           d } nT|s!rI                    dpd|Idrgdz           t                     d D              |             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  r@   r  ro  rB  r  )r   ri   rj   rC  r  ri  )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  rM  r  r  s      r&   rO  zzget_available_models.<locals>._build_available_models_uncached.<locals>._build_configured_model_badges.<locals>.<listcomp>
  sU    fffa155SUCVCVffaZ[Z_Z_`dZeZef!%%b//ffffr(   c                 H    i | ]}t          |          t          |           S rM   )r   )rM  opt_ids     r&   
<dictcomp>zzget_available_models.<locals>._build_available_models_uncached.<locals>._build_configured_model_badges.<locals>.<dictcomp>
  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   )rg   r   r  s      r&   r  zzget_available_models.<locals>._build_available_models_uncached.<locals>._build_configured_model_badges.<locals>.<dictcomp>
  s     & & &x,,& & 55;;	&AEE$KK  #aeeM&:&:&@b"A"A& & & &r(   ro  r  r@   c              3   0   K   | ]}|v |         V  d S r  rM   )rM  coption_lookups     r&   rW  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  )rM  r  option_provider_lookupr   s     r&   rW  zyget_available_models.<locals>._build_available_models_uncached.<locals>._build_configured_model_badges.<locals>.<genexpr>9
  s=      ]]13I3M3Ma3P3PT\3\3\Q3\3\3\3\]]r(   r   r  r   )r  r   r   )r   rK   rg   re   r   r  rf   r?  r   ri   r  next)configured_entriesfallback_cfgidxrN  r  
option_idsr  r  badgesraw_candidatesr   match_idexact_matchro   matchesprovider_matchbadge_payloadcandidate_providerr  r  r   r  r  r>  r  s                     @@@r&   _build_configured_model_badgeszfget_available_models.<locals>._build_available_models_uncached.<locals>._build_configured_model_badges	  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   rC   r_  r_  r   rr   rs   r  z!Failed to load auth store from %scredential_pool)	load_poolrk  c                     g | ]o}t          t          t          |d d          pd          t          t          |dd          pd          t          t          |dd          pd                    m|pS )rP  r   r   r  )r  r   rt  )rM  r   s     r&   rO  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 )rP  r   r   r  N)re   rf   r  r   rg   )rM  _entrys     r&   rW  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 hermesrQ   r   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_KEYr  r  r  r  r  r  r
  r  r  r  r)  r  r  r  r  r   r  r!  r  r  r  r  r$  r"  r"  r#  r   r  r  r  r  
LM_API_KEYLM_BASE_URLr  r  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  r  rJ  r6   r8  )rx  re   rf   rg   rf  rK   r   ri   rj   r  r?  r   rG  )r_  rz  model_base_urlr  r0  provider_keyprovider_cfgprovider_base_urlcustom_providers_cfgrN  r{  r\  r  s               r&   !_configured_provider_for_base_urlziget_available_models.<locals>._build_available_models_uncached.<locals>._configured_provider_for_base_url4  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(   z/v1z/modelsro  z
/v1/modelsr8  rp  rq  r  r   r  r  r*  )HERMES_API_KEYHERMES_OPENAI_API_KEYr  LOCAL_API_KEYr  API_KEYzBearer AuthorizationrJ  )r   rr  httpszInvalid URL scheme: c              3   R   K   | ]!}|j         pd                                 v V  "dS )r   N)r  rj   )rM  krw  s     r&   rW  zQget_available_models.<locals>._build_available_models_uncached.<locals>.<genexpr>  sP       	5" 	5"() %&**=*C)J)J)L)L$L	5" 	5" 	5" 	5" 	5" 	5"r(   )r  r  r   r  r  z&SSRF: resolved hostname to private IP GET)methodz
User-AgentzOpenAI/Python 1.0
   timeoutr=   r  r6   r  r|  z=Custom endpoint unreachable or misconfigured for provider: %sr@   r  rA  c              3      K   | ]C}t          |t                    o)|                    d           pd                                 V  DdS )r6   r   N)re   rf   rg   ri   )rM  _cps     r&   rW  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  )rl  rM  r4  s     r&   rW  zQget_available_models.<locals>._build_available_models_uncached.<locals>.<genexpr>B  sA       , ,:;-a005A, , , , , ,r(   )r   r  r  )fetch_openrouter_modelsz9Failed to load OpenRouter curated catalog from hermes_cliz#https://openrouter.ai/api/v1/modelsAcceptr   )headersg       @   pricingprompt0
completionz:freer  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  )rM  r  s     r&   rO  zRget_available_models.<locals>._build_available_models_uncached.<locals>.<listcomp>  sI     & & & ! uuZ00L@@ $%T7QwZ@@@@@r(   r  r  r  c                 2    g | ]}|t          |          d S r|  )r  rM  r  s     r&   rO  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 rM   rM   rJ  s     r&   rO  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 rI  )r  rJ  s     r&   rO  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  rJ  s     r&   rO  zRget_available_models.<locals>._build_available_models_uncached.<locals>.<listcomp>9  s?     & & & # $2C>><Ns<S<STT& & &r(   c                 8    g | ]}d | t          |          dS rN  rO  rJ  s     r&   rO  zRget_available_models.<locals>._build_available_models_uncached.<locals>.<listcomp>=  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 rI  rM   rJ  s     r&   rO  zRget_available_models.<locals>._build_available_models_uncached.<locals>.<listcomp>}  s     %R%R%RCS3&?&?%R%R%Rr(   )r6  rA     z$LM Studio /models fetch failed at %sc                     g | ]}||d S rI  rM   r>  s     r&   rO  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|  )re   rf   rg   r>  s     r&   rO  zRget_available_models.<locals>._build_available_models_uncached.<locals>.<listcomp>  s     *@ *@ *@/0 =Gq$<O<O1V4UVPZ[\^bPcPc5jQUU7AdG5L5L5Lij+l +l *@ *@ *@r(   Defaultrh  rB  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  )rM  r  r  r  s      r&   rU  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   rA  )rg   rC  )rM  r  s     r&   rO  zRget_available_models.<locals>._build_available_models_uncached.<locals>.<listcomp>,  sa     
 
 
uuX
 m$$*66yAA

 
 
r(   r  )frA  rK   r   rf   rg   re   rf  rz  r    rD   r  ry   rF   r{   r|   r  r   agent.credential_poolr  r   r  r?  rn  r  rQ  	monotonicrT   r  r   anyr=  r  hermes_cli.authr  rS   rR   r   
splitlinesri   rC  r  r   r   rl  rk  objectr  urllib.requestr'  rs  rS  r   ru  r%   rj   r  r  r  r  r  r  filtersocketrt  getaddrinfogaierrorrequestRequest
add_headerurlopenreaddecoder  r   r  rG  r  discardrX  rb  rz   intersectionr  rj  titler?  warningr  	TypeError_FALLBACK_MODELSr  r  r  r  r  r  r  r  r  ri  insertr  )r  cfg_base_urlr?  
auth_storeauth_store_pathr  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liner4  r  val_cfg_providers_pid_key
_canonicalr,  auto_detected_models auto_detected_models_by_providerr  urllibr_  endpoint_urlconfigured_providerr   provider_from_configparsedr  r  rA  r*  r0  r(  r)  api_key_varsr  ra  _ssrf_trusted_hosts_base_parsed_custom_providers_cfgr;  _cp_base
_cp_parsedresolved_ipsrh  addr_objis_known_localreqresponser=   models_listr  r  
model_namer   
auto_model_named_custom_groups_seen_custom_ids_cp_name_slug_cp_model_ids	_cp_model_cp_models_dict_m_id
_dedup_key	_cp_label_cp_option_id_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  seen_ids_fetch_or_modelslive_curatedr  _desc_urlreq_req_resp_payload_free_count	_free_cap_item_mid_pricing_is_free_name_labelr  r  	codex_idsrQ  truncated_label_suffixlive_fetch_failedr  
_model_cfg	_selectedfeatured_ids
extras_idsr  group_entrylm_idslm_cfglm_base_url
lm_api_keyendpointresplm_datar  
cfg_modelsdetected_modelsmodels_for_group_looks_like_provider_idall_ids_normtarget_displayinjectedr  r  r  r>  r  r  rw  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 ! K	3<HHHHHH"'%''F"2 < < <"&"2V";<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{{<(( 3W[[-G-G 3"&&z222 b11nd++ 	7* 7 76x@@
! !111Z>5Q5QU]aoUoUo&**:666*	 *	3 *	 *	 *	 *	 *	 *	Z  "BD( R	hQh    %%%%'--//$$U++ G#+i#7LL#+??3#7#7,#FL&G&G&Q&Q#.:('+,?'@'@$!ex.?.?((EYxEYEYZZ4;;==? +? (33FODD? 4d.> 4$BT 4'4//;$3F3F+Y]J]J]+3!+t!3!3{d7J7J+5 ,4%    i.. G(}}Y77=2DDFFG *$'GGK$<$<M!-66 *,24/89T,U,U * *L+8+<+<\2+N+NL),== *+7+;+;I+F+F+L"*S*S*U*U#* !*$)E "$L  , " "#*;;s#3#3#Kry~~#K"R"R"T"T" "!E" C/B/B/BGO, 14# O#+E\<Q<QLLWoamWoWo#p#pL#, O+//0E0K0K0M0MNNN(+0BB(G(G%3T:: U4 U U)#t44 %$$'GGJ$7$7$=2#D#D#F#F# U)1ex>O>O((Ui_gUiUi)j)jJ)2 U 3 7 7
8K8Q8Q8S8S T T T%$)\$9$9LL?W?W?W 
 $,AAA$%OJ<M%O%OPPP& '-'9'9*:Mt'T'T0< & &,Aq!Q'0';';DG'D'DH'2 &h6J &hNd &14 	5" 	5" 	5" 	5".&	5" 	5" 	5" 	2" 	2" 	2` '1&9&?R%F%F%H%HL_%_ !/ (6 !&*4(ZQUVWQX(Z(Z+& +& %&&" "?   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 !T>>jft&D&D>"&v,KK%%*T(^T*J*J%"&x.K( = =E%eT22 ! 		$++ 2 99VR002 99Wb11 
 "'62!6!6!\%))GR:P:P!\T\J =J =BJNhBhBh 4X > > >nx,4u%E%E
,33J???'/~~'7'78CCLRTUU\\]ghhh*..|<<<=   h h h\^fgggggh !$(:B ? ?%'+T22 $	="uu, "= "=!#t,, GGFOO1r88::EMW7AAASW AU*>>>3;R.(/ ,.GGGR00	 4!((333"%''("3"3ot44 @!0 @ @%eS11 @ekkmm @UbHbHb)00???!. = =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  Y	011 R R >>),, 2222Fs2K/Z  * W)I)M)McSU)V)VJ% o"MM{SVbl*m*mnnn 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    ,,,sww{B7O7O0O0O#&77;#;#;#?#?R#H#HL!#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*:   YRh  ,]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&&&
 

 
 
  /*'E'E'G'G	
 
 	
s  2AF %F43F4AQ
 $*N, D.M?=N, ?%N($N, 'N((N, +Q
 ,BQQ
 QQ
 
$Q10Q19.V (+SV 9TV T!V 33U' &V '$VV VV $V98V9 W W.-W.C[ $[21[2,B.AC8 Am' &AC8 '
m41AC8 3m44JAC8 B(z5 4AC8 5
{?AC8 {BAC8 :~	=AC8 	~AC8 ~E&AC8 C8%AD DAD Y9AA[[$A[.[-A[.[26Ad9\(9A]-]!Ad9]-A]1	]1Ad9]4A]1	]5BAd9`AAaaAd9aAa+a(Ad9a*Aa+a+CAd9d9$Ae eAe fAf8f8$AggAghAh9h9$Ai iAi lAll(AmmAmr
Arr$AssAsv5Azw9Axw?AzxAx	xAzxAx	xB	Azz%A{{A{Nc                  $    t            ot          d uS r  )r  rh  rM   r(   r&   r  z&get_available_models.<locals>.<lambda>S  s    44\9PX\9\ r(   <   r8  TF)rV   rZ   r[   r\   r:   r;   rN   r]   rf   r  r   rh  r  r  r  wait_forr  r  rZ  ri  rj  r  r  r  r  rF   r  )	_current_path_current_mtimer  should_wait_cfg_changeddisk_groupscachedr  r  s	            r&   get_available_modelsr  	  s   &(**&++--6   (** 
:	%	%))C)C,.. *D 	U
d U
 U
 U
 U
v" +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                   h    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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  r   )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  r   r  r  r   rv   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)r   
__module____qualname____doc__r  r  r  r  r  tupler   r^  r  rM   r(   r&   r  r    s         < < <
5;    U[ T    uS&[1 d      r(   r  c                      t                      S r  )r  rM   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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)r  rf   r  ACTIVE_RUNS_LOCKr  )r  metadatar  rN  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  rg   rz   )r  r  rN  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  r  r  )r  s    r&   unregister_active_runr    s     	 + +	4(((#y{{+ + + + + + + + + + + + + + + + + +s   /AAASESSION_AGENT_CACHE2   
session_idc                 |    t           5  t                              | d           ddd           dS # 1 swxY w Y   dS )zHRemove a cached agent for a session (on delete, clear, or model switch).N)SESSION_AGENT_CACHE_LOCKr  r  r   s    r&   _evict_session_agentr$    s    	! 2 2
D1112 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2s   155c                      | t           _        d S r  _thread_ctxenv)kwargss    r&   _set_thread_envr*    s    KOOOr(   c                      i t           _        d S r  r&  rM   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_lockr0    s    ( 
" / /000.7n.>.>
+":./ / / / / / / / / / / / / / / / / /s   1AA
A
default_workspaceonboarding_completedsend_keyentershow_token_usageshow_tpsshow_cli_sessionssync_to_insightscheck_for_updatesthemedarkskinrC   	font_sizesession_jump_buttonssession_endless_scrolllanguageenbot_nameHERMES_WEBUI_BOT_NAMEHermessound_enablednotifications_enabledcompactrE  r  )show_thinkingsimplified_tool_callingapi_redact_enabledsidebar_densityauto_title_refresh_everybusy_input_modepassword_hash>   bubble_layoutr>  assistant_language>   r;  lightsystem>   aresmonoslaterC   poseidonsisyphus	charizard)r;  rU  )r;  rV  )r;  rW  r;  rC   )rU  	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   rC   rY  )re   r   ri   rj   _SETTINGS_LEGACY_THEME_MAPrg   _SETTINGS_THEME_VALUES_SETTINGS_SKIN_VALUES)r:  r<  	raw_themeraw_skinlegacy
next_themelegacy_skin	next_skins           r&   _normalize_appearancerh  a  s    ( *4E3)?)?G##%%%RI'1$'<'<Dtzz||!!###"H'++I66F 4"(
KK	,	,	,"+YK

 #4
K ,,, 	 
 y  r(   c                  b   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
<   | S )zDLoad settings from disk, merging with defaults for any missing keys.Nz,Cannot stat settings file %s (inaccessible?)Frr   rs   c                 ,    i | ]\  }}|t           v||S rM   )_SETTINGS_LEGACY_DROP_KEYSrM  r4  r  s      r&   r  z!load_settings.<locals>.<dictcomp>  s4        Aq$>>> 1>>>r(   zFailed to load settings from %sr:  r<  r>  )rf   _SETTINGS_DEFAULTSSETTINGS_FILEr    r\   r{   r|   rD   r  ry   re   rz   r  rF   rh  rg   rA  )settingsstoredsettings_existss      r&   load_settingsrr    s   &''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_Os"   2 ,A! A!'A3C *DDrN  r>  z
ctrl+enterdetailed>   largesmallrC   >   rE  51020>   r  steer	interrupt)r3  rK  r=  rL  rM  >   r6  rH  rE  r5  r8  r9  r7  rJ  r2  r>  rF  r?  rI  rD  z$^[a-zA-Z]{2,10}(-[a-zA-Z0-9]{2,8})?$ro  c                    t                      }|                    d          }|                    d          }d}d}|                     dd          }|rOt          |t                    r:|                                r&ddlm}  ||                                          |d<   |                     d	d          rd|d<   |                                 D ]\  }}	|t          v r|dk    r.t          |	t                    r|	                                r|	}d
}B|dk    r.t          |	t                    r|	                                r|	}d
}v|t          v r|	t          |         vr|dk    r0t          |	t                    rt                              |	          s|t          v rt          |	          }	|	||<   |}
|}|rJ|sHt          |t                    r&|                                                                nd}|t           vrd}t#          |
|          \  |d<   |d<   t	          t%          |                    d                              |d<   d |                                D             }t&          j                            d
d
           t&                              t/          j        |dd          d           d|v rt%          |d                   at5                      |d<   |S )zISave settings to disk. Returns the merged settings. Ignores unknown keys.r:  r<  F_set_passwordNr   )_hash_passwordrN  _clear_passwordTr@  r   r1  c                 &    i | ]\  }}|d k    ||S )r>  rM   rl  s      r&   r  z!save_settings.<locals>.<dictcomp>  s(    JJJ$!QQ/5I5IA5I5I5Ir(   r   r  ensure_asciir  rr   rs   r>  )rr  rg   r  re   r   ri   api.authr}  r  _SETTINGS_ALLOWED_KEYS_SETTINGS_ENUM_VALUES_SETTINGS_LANG_REmatch_SETTINGS_BOOL_KEYSrS  rj   r`  rh  r   rn  r   r   r   rD   rE   r   rA  )ro  currentpending_themepending_skintheme_was_explicitskin_was_explicitraw_pwr}  r4  r  theme_value
skin_valuerb  	persisteds                 r&   save_settingsr    s    ooGKK((M;;v&&L\\/400F B*VS)) Bfllnn B++++++#1>&,,..#A#A ||%u-- (#'     1&&&G||a%% .!'')) .$%M)-&F{{a%% -!'')) -#$L(,%)))a7LQ7O.O.OJq#&& .?.E.Ea.H.H  '''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Nr(   r   r  r  rr   rs   SESSIONS)init_profile_stater  )r   N)r  collectionsr  rD   loggingr   r  rD  rr  r  r  	tracebackuuidpathlibr   r  r   r   homer   __file__r   r   r   r   r   r  r   ri   TLS_CERTTLS_KEYTLS_ENABLEDr   r   r   SESSION_DIRWORKSPACES_FILESESSION_INDEX_FILErn  LAST_WORKSPACE_FILEPROJECTS_FILE	getLoggerr   r{   r'   r8   r   r   r%   r   r   rJ   r  ru   r:   r  __annotations__r;   r<   rf   rH   rS  rN   rV   rk   rh   ra   rp   r]   r   r   rK   r   r   r   r   r   r   r=  r   r  r   MAX_FILE_BYTESMAX_UPLOAD_BYTES
IMAGE_EXTSMD_EXTS	CODE_EXTSMIME_MAPr   r   CLI_TOOLSETSro  rj  r<  r?  r^  rG  rR  r  rX  r]  rf  rl  rx  rb  rk  	frozensetr  r  r  r  r  _NOUS_FEATURED_TARGETr  r  r  r  r  r  r  r  r  r!  r6  r8  rA  rK  rM  rX  r[  r]  rg  rh  ri  rj  rl  RLockr  	Conditionr  r  rn  ro  rv  r  r  rz  r  r  r}   r  r  r  r  r  r_  r  r  r  r  r  r  r  _INDEX_HTML_PATHLOCKSESSIONS_MAX	CHAT_LOCKr  r  r  STREAMS_LOCKr	  r
  r  r  r  r  r  r  r  r  SERVER_START_TIMEr  r  r  OrderedDictr  SESSION_AGENT_CACHE_MAXr"  r$  r:  r'  r*  r,  r-  r/  r0  rm  rk  r`  ra  r_  rh  rr  r  r  r  r  r   compiler  r  _startup_settingsr    _settings_file_existsr\   rg   r  r   rE   rF   r  rS   r  rT   rM   r(   r&   <module>r     s`#  	 	 	        				  				 



                 + + + + + + + + ty{{DNN!(0022	 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': 	D+SS	1AG1K-L-L	M	MNN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    ,
0$ 
0 
0 
0 
0 (1 #-  D    ., ,TD[ ,C , , , ,,+ + + +D      
 
4 
D 
 
 
 
  sTzD0 DJ    >      3:#4     	'T 	' 	' 	' 	' 0/11 	6;;!
 !
 !
 !
H0u 0 0 0 0( #  POO

(
(
(  	6
K
L \ K	
 O \ N K   [ K & P    V!" L#$ ;  B    
N 
N 
N 
N %$&&.$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  h!" N#$ # 3   R+	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 
( 
( 
( 
( # t 	    r +<== +<=="-@AA"-@AA!,>??r 	227777	22	r 9--779--779999"-@AA#.BCC"-BCC
r2 '=UVV'=UVV.=\]]=MNN!=OPP3r@ "-@AA +<==&1GHH"-IJJ	ArL 0?[\\2?]^^*?XYY4?bcc	MrX 
9--))}559--9--99Yrh *:;; +<==!,>??}55[11irv 77'2JKK77'2JKK77wrD 777777l33	ErR 9--779--77(++ +<=="-@AA'2JKK	Srh  %}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%irv I66G44K88K88 ,=>>",?@@M::N;;O<<K88N;;N;;N;;N;;wr\ '=UVV'=UVV.=\]]=MNN!=OPP]rl %@@%@@mrv 7788wr@ 2=TUU/:OPP9Dbcc1<LMM	ArP O<<K88M::N;;O<< [11_r r r j $)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 {6|]C ]E#*cTXjBX<Y ] ] ] ]@!" !"# !"sTz !"UX !" !" !" !"H TD[ C    2 H   $d    (" " " " " "(" " " " " "8<2s <2t <2 <2 <2 <2@ (,  + + +$' 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 .d . . . . $    $$       & T    .16 1d 1 1 1 1hdTk    >#d #t # # # #L $+    4# # #>	O# 	O 	O 	O 	O## # # # #43  #    B)s )tCy ) ) ) )X tCy TRVZ    &T#Y & & & &RA%d A% A% A% A%J& x',6  y~IN	* * * * * * * *Z}        y~d       T     t      !  ! ! ! T   !$ 3 & & &
 T   !9>## %) edl ) ) )DIKK 
'3 
't 
' 
' 
' 
'# #T # # # #+S +T + + + +    /F{/F/H/H [, H H H )9>++ 2S 2T 2 2 2 2 io    
  T   )9>++ / /	 / / / /8.//E  	
     V I  E e  		 $ U%& U'( #  #5   8 VUU 444     %#  #!%S/ #! #! #! #!Lt    @ /446677;  
 ,'!:.... 6 6 6666       Jt$$,,-TUU ?D ?T ? ? ? ?N "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	s6   #v8 8ww,y8 8z ?z z1 1z:9z: