
    (Gj                       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mZ ddl	m
Z
mZmZmZmZ ddlmZ ddlZddlZddlmZmZmZ ddlmZ  ej        e          Zdeez  fd	Z eh d
          Z ee         e!d<    ej"        dej#                  Z$ ej%        d          Z&dedefdZ'i a(eeeee
f         f         e!d<   da)e*e!d<   i Z+eeeee
f         f         e!d<   dZ,e*e!d<   dZ-i Z.eeeeeee
f         f         f         e!d<   i Z/eee*f         e!d<   dZ0defdZ1dee*         fdZ2deeeee
f         f         fdZ3deeeee
f         f         ddfdZ4g dZ5e5d         Z6dZ7dZ8i Z9ee:e:f         e!d <   i d!d"d#d"d$d"d%d"d&d"d'd"d(d"d)d"d*d"d+d"d,d-d.d/d0d1d2d1d3d/d4d5d6d5i d7d1d8d9d:d5d;d<d=d>d?d>d@d>dAdBdCdDdEd"dFd"dGd"dHd"dId5dJdBdKd<dLd"i dMdNdOdBdPd"dQdRdSd<dTdUdVd-dWd>dXd>dYdDdZd[d\d[d]d"d^d>d_dBd`dBdadBi dbdNdcdNdddBdedNdfdNdgdBdhdBdidjdkdNdldNdmdNdndRdodNdpd<dqd<drd<dsdNdNdUdtZ;duZ<dedefdvZ=dwZ>dxZ?dyZ@dzZAd{edefd|ZBdd~edeeef         fdZCd{edefdZDd{edefdZEi ddddddddddddddddddddddddQdddddddddd;i ddddIddddddddddddddddddddddddddddZFeeef         e!d<   	 ddlGmHZI  eI            D ]&ZJeJK                                ZLeLreLeFvr
eJjM        eFeL<   'n# eN$ r Y nw xY wd{edee         fdZOd{edefdZPd{edefdZQd{ededefdZRded{edeSddfdZT	 dded{edeSd~edeSf
dZUd{edefdÄZVdd{ed~edee         fdĄZWde
fdƄZXdde
deSdeSdeeS         fd˄ZYdeee
f         de:edf         deeS         fdτZZdeee
f         deeS         fdЄZ[deee
f         deeS         fdфZ\deee
f         deee
f         fd҄Z]deeeee
f         f         dedeee
f         ddfdքZ^ddedeeeee
f         f         fdلZ_	 	 dd{ed~ededeeeee
f         f         fdڄZ`	 dded{ed~edeeS         fdۄZadefd܄ZbdeeeSf         fd݄Zcded{edeSddfdބZdded{edeeS         fd߄Zeded{eddfdZfdeSdeeS         fdZgdedeeS         fdZhdedeSdeeS         fdZidedeeS         fdZjdedefdZkdededefdZldded{ed~edeeS         fdZmdded{ed~edee         fdZndded{ed~edeeS         fdZodedefdZpdedefdZqdedefdZrdded{ed~edeeS         fdZsdded{ed~edeeS         fdZtdedefdZuded{ed~edeeS         fdZvdddd5ddddddd
ZweeeSf         e!d<   i axeeeSf         e!d<   daye*e!d<   dZzdedeeeSf         fdZ{	 ddededeeS         fdZ|	 	 dded{ed~edeeeS         ef         fdZ}	 	 	 	 	 dded{ed~ed eSdz  dede~dz  deSfdZ	 	 	 	 	 dded{ed~ed eSdz  dede~dz  deSfdZdedeSfdZdeeee
f                  deSfdZdeee
f         d	eSdeSfd
Zdeee
f         deSfdZd}dddeeee
f                  dedeeeee
f                           deSfdZdS (  zModel metadata, context lengths, and token estimation utilities.

Pure utility functions with no AIAgent dependency. Used by ContextCompressor
and run_agent.py for pre-flight context checks.
    N)Path)AnyDictListOptionalTuple)urlparse)atomic_json_writebase_url_host_matchesbase_url_hostname)OPENROUTER_MODELS_URLreturnc                      dD ];} t          j        |           }|r#t           j                            |          r|c S <dS )a  Resolve SSL verify setting for `requests` calls from env vars.

    The `requests` library only honours REQUESTS_CA_BUNDLE / CURL_CA_BUNDLE
    by default. Hermes also honours HERMES_CA_BUNDLE (its own convention)
    and SSL_CERT_FILE (used by the stdlib `ssl` module and by httpx), so
    that a single env var can cover both `requests` and `httpx` callsites
    inside the same process.

    Returns either a filesystem path to a CA bundle, or True to defer to
    the requests default (certifi).
    )HERMES_CA_BUNDLEREQUESTS_CA_BUNDLESSL_CERT_FILET)osgetenvpathisfile)env_varvals     :/home/rurouni/.hermes/hermes-agent/agent/model_metadata.py_resolve_requests_verifyr      sO     O  i   	27>>#&& 	JJJ4    >I   x-aix.aiz-aiz.aikimi-cnarcee-ai	deep-seek	gmi-cloud	novita-ai
minimax-cn
nvidia-nimmoonshot-cnqwen-portalxiaomi-mimoopencode-zengithub-modelsgoogle-geminiminimax-oauthtencent-cloudgoogle-ai-studiogoglmgminimxaizaizengrokkilokimimimonousqwenarceelocalzhipualiyunclaudecustomgeminigithubgooglenovitanvidiaollamaxiaomialibabaarceeaicopilotminimaxstepfuntencentdeepseekgmicloudkilocodemoonshotnemotronnovitaaiopencodetokenhub	anthropic	dashscope
openroutertencentmaas
qwen-oauthcopilot-acpkimi-codingopencode-goollama-cloudopenai-codexgithub-copilotkimi-coding-cntencent-tokenhub_PROVIDER_PREFIXESzE^(\d+\.?\d*b|latest|stable|q\d|fp?\d|instruct|chat|coder|vision|text)z100.64.0.0/10modelc                 ,   d| vs|                      d          r| S |                     dd          \  }}|                                                                }|t          v r0t
                              |                                          r| S |S | S )ua  Strip a recognised provider prefix from a model string.

    ``"local:my-model"`` → ``"my-model"``
    ``"qwen3.5:27b"``   → ``"qwen3.5:27b"``  (unchanged — not a provider prefix)
    ``"qwen:0.5b"``     → ``"qwen:0.5b"``    (unchanged — Ollama model:tag)
    ``"deepseek:latest"``→ ``"deepseek:latest"``(unchanged — Ollama model:tag)
    :http   )
startswithsplitstriplowerre   _OLLAMA_TAG_PATTERNmatch)rf   prefixsuffixprefix_lowers       r   _strip_provider_prefixrt   W   s     %5++F33[[a((NFF<<>>''))L)))$$V\\^^44 	LLr   _model_metadata_cache_model_metadata_cache_time_novita_metadata_cache_novita_metadata_cache_timei  _endpoint_model_metadata_cache#_endpoint_model_metadata_cache_timei,  c                  .    ddl m}   |             dz  dz  S )z8Return path to the OpenRouter model metadata disk cache.r   get_hermes_homecachezopenrouter_model_metadata.jsonhermes_constantsr}   r|   s    r   _get_model_metadata_cache_pathr   t   s-    000000?w&)IIIr   c                      	 t                      } |                                 sdS t          j                    |                                 j        z
  }|dk     rdS |S # t
          $ r Y dS w xY w)zBReturn disk-cache age in seconds, or None if freshness is unknown.Nr   )r   existstimestatst_mtime	Exception)
cache_pathages     r   &_model_metadata_disk_cache_age_secondsr   z   s}    	355
  "" 	4ikkJOO--66774
   tts   "A 3A A 
A+*A+c                  x   	 t                      } |                     dd          5 }t          j        |          }ddd           n# 1 swxY w Y   t	          |t
                    si S d |                                D             S # t          $ r'}t          	                    d|           i cY d}~S d}~ww xY w)z3Load processed OpenRouter metadata cache from disk.rutf-8encodingNc                 ^    i | ]*\  }}t          |t                    t          |          |+S  )
isinstancedictstr.0keyvalues      r   
<dictcomp>z3_load_model_metadata_disk_cache.<locals>.<dictcomp>   sD     
 
 
U%&&
HHe
 
 
r   z7Failed to load OpenRouter model metadata disk cache: %s)
r   openjsonloadr   r   itemsr   loggerdebug)r   fdataes       r   _load_model_metadata_disk_cacher      s   355
__S7_33 	 q9Q<<D	  	  	  	  	  	  	  	  	  	  	  	  	  	  	 $%% 	I
 
"jjll
 
 
 	

    NPQRRR						sE   %B AB AB AB *B 
B9B4.B94B9r   c                     	 t          t                      | dd           dS # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)z<Save processed OpenRouter metadata cache to disk atomically.r   ),rh   )indent
separatorsz7Failed to save OpenRouter model metadata disk cache: %sN)r
   r   r   r   r   )r   r   s     r   _save_model_metadata_disk_cacher      s    S*,,!		
 	
 	
 	
 	
 	
  S S SNPQRRRRRRRRRSs   # 
AAA)       i }  i>  i@  r   g      >@_LOCAL_CTX_PROBE_CACHEzclaude-fable-5@B zclaude-fablezclaude-opus-4-8zclaude-opus-4.8zclaude-opus-4-7zclaude-opus-4.7zclaude-opus-4-6zclaude-sonnet-4-6zclaude-opus-4.6zclaude-sonnet-4.6rA   i@ gpt-5.5i zgpt-5.4-nano gpt-5.4-minigpt-5.4gpt-5.3-codex-sparkr   zgpt-5.1-chatgpt-5zgpt-4.1i zgpt-4rC   i   zgemma-4r   gemma4zgemma-4-31bzgemma-3i   gemmai    zdeepseek-v4-prozdeepseek-v4-flashzdeepseek-chatzdeepseek-reasonerrP   llamazqwen3.6-pluszqwen3-coder-pluszqwen3-coderi   r<   
minimax-m3rM      zglm-5.2r1   i  zgrok-composerz
grok-buildzgrok-code-fastzgrok-2-visionzgrok-4-fasti z	grok-4.20grok-4.3zgrok-4zgrok-3zgrok-2r7   r9   zhy3-previewrT   trinityelephantzQwen/Qwen3.5-397B-A17BzQwen/Qwen3.5-35B-A3Bzdeepseek-ai/DeepSeek-V3.2i   zmoonshotai/Kimi-K2.5zmoonshotai/Kimi-K2.6zmoonshotai/Kimi-K2-ThinkingzMiniMaxAI/MiniMax-M2.5zXiaomiMiMo/MiMo-V2-Flashzmimo-v2-prozmimo-v2.5-proz	mimo-v2.5zmimo-v2-omni)zmimo-v2-flashzzai-org/GLM-5)zgrok-3-minizgrok-4.20-multi-agentr   c                     | pd                                                                 sdS dD ]"}|v r                    |d          d         #t          fdt          D                       S )a$  Return True when an xAI Grok model accepts ``reasoning.effort``.

    Allowlist by substring (matches both bare ``grok-3-mini`` and
    aggregator-prefixed ``x-ai/grok-3-mini``). Conservative by design:
    if a future Grok model isn't listed, we send no effort dial rather
    than 400.
     F)/rj   c              3   B   K   | ]}                     |          V  d S N)rk   )r   rq   names     r   	<genexpr>z1grok_supports_reasoning_effort.<locals>.<genexpr>n  s/      SS6tv&&SSSSSSr   )rm   rn   rsplitany_GROK_EFFORT_CAPABLE_PREFIXES)rf   sepr   s     @r   grok_supports_reasoning_effortr   _  s     KR  &&((D u + +$;;;;sA&&r*DSSSS5RSSSSSSr   )context_lengthcontext_windowcontext_sizemax_context_lengthmax_position_embeddingsmax_model_lenmax_input_tokensmax_sequence_lengthmax_seq_lenn_ctx_trainn_ctxctx_size)max_completion_tokensmax_output_tokens
max_tokens)	localhostz	127.0.0.1z::1z0.0.0.0)z.docker.internalz.containers.internalz.lima.internalbase_urlc                 T    | pd                                                     d          S )Nr   r   )rm   rstripr   s    r   _normalize_base_urlr     s&    N!!##**3///r   r   api_keyc                 ^    t          | pd                                          }|si S dd| iS )Nr   AuthorizationBearer )r   rm   )r   tokens     r   _auth_headersr     s@    2$$&&E 	.u..//r   c                 "    t          | d          S )Nopenrouter.ai)r   r   s    r   _is_openrouter_base_urlr     s     ?;;;r   c                 ^    t          |           }t          |          ot          |           S r   )r   boolr   )r   
normalizeds     r   _is_custom_endpointr     s.    $X..J
G$;J$G$G GGr   zapi.openai.comopenaizchatgpt.comapi.anthropic.comrX   zapi.z.air5   zopen.bigmodel.cnzapi.moonshot.air^   zapi.moonshot.cnrc   zapi.kimi.comzapi.stepfun.airN   zapi.stepfun.comzapi.arcee.air=   zapi.minimaxzdashscope.aliyuncs.comrJ   zdashscope-intl.aliyuncs.comzportal.qwen.air\   r   rZ   z!generativelanguage.googleapis.comzinference-api.nousresearch.comr;   zapi.deepseek.comzapi.githubcopilot.comrL   z.githubcopilot.comzmodels.github.aizmodels.inference.ai.azure.comzapi.fireworks.ai	fireworkszopencode.air_   zapi.x.air4   zintegrate.api.nvidia.comrG   zapi.xiaomimimo.comrI   zxiaomimimo.comzapi.gmi-serving.comr2   api.novita.airF   ztokenhub.tencentmaas.comrd   z
ollama.comr`   _URL_TO_PROVIDER)list_providersc                    t          |           }|sdS t          d|v r|nd|           }|j                                        p|j                                        }t
                                          D ]\  }}||v r|c S dS )a  Infer the models.dev provider name from a base URL.

    This allows context length resolution via models.dev for custom endpoints
    like DashScope (Alibaba), Z.AI, Kimi, etc. without requiring the user to
    explicitly set the provider name in config.
    N://zhttps://)r   r	   netlocrn   r   r   r   )r   r   parsedhosturl_partproviders         r   _infer_provider_from_urlr     s     %X..J tEZ$7$7jj=T
=T=TUUF=  7FK$5$5$7$7D.4466  (tOOO 4r   c                     t          |                               d          }dD ]D}|                    |          r-|dt          |                                        d          } nE|S )zBReturn the LM Studio server root for native ``/api/v1`` endpoints.r   )z/api/v1z/api/v1N)r   r   endswithlen)r   rootrr   s      r   _lmstudio_server_rootr     su    x((//44D,  ==   	3v;;,'..s33DE	 Kr   c                 $    t          |           d uS r   )r   r   s    r   _is_known_provider_base_urlr     s    #H--T99r   r   c                     |dk    S )u   Return True when the on-disk context cache must not short-circuit probing.

    LM Studio excludes caching because loaded context is transient — the user
    can reload the model with a different context_length at any time.
   lmstudior   )r   r   s     r   _skip_persistent_context_cacher    s     z!!r   lengthc                 B    |t           k    rt          | ||           dS dS )ub  Persist a locally probed context length only when it meets Hermes minimum.

    Sub-minimum live windows (e.g. vLLM ``--max-model-len 32768``) are still
    returned to callers so ``agent_init`` can fail with the existing
    minimum-context guidance — they must not be normalized into the on-disk cache
    as if they were valid operating limits.
    N)MINIMUM_CONTEXT_LENGTHsave_context_length)rf   r   r  s      r   !_maybe_cache_local_context_lengthr    s0     '''E8V44444 ('r   cachedc                 X   t          | ||          }|r|dk    r||k    r|t          k     r9t                              d| ||dt          d           t	          | |           |S t                              d| ||d|d           t	          | |           t          | ||           |S |S )u  Return *cached* unless a live local probe reports a different limit.

    vLLM/Ollama operators can restart with a new ``--max-model-len`` / ``num_ctx``
    without changing the model id.  When the server is reachable, prefer its
    reported window over a stale disk entry; when the probe fails (offline tests,
    network blip), keep the cached value.

    Live probes below :data:`MINIMUM_CONTEXT_LENGTH` invalidate stale cache
    entries but are not persisted — startup should reject them, not bless a
    sub-64K window as config.
    r   r   uk   Live local probe for %s@%s reports %s (< minimum %s); invalidating stale cache — agent init should rejectr   z@Reconciling stale local cache entry %s@%s: %s -> %s (live probe))_query_local_context_lengthr  r   info!_invalidate_cached_context_lengthr  )rf   r   r	  r   live_ctxs        r   &_reconcile_local_cached_context_lengthr    s    " +5(GLLLH HqLLX%7%7,,,KKHxH5K2O2O  
 .eX>>>ON8]]xOO	
 	
 	
 	*%:::)%8DDDMr   c                    t          |           }|sdS d|v r|nd| }	 t          |          }|j        pdn# t          $ r Y dS w xY wt          v rdS t          fdt          D                       rdS rdvrdS 	 t          j                  }|j	        s|j
        s|j        rdS t          |t          j                  r|t          v rdS n# t          $ r Y nw xY w                    d          }t#          |          dk    r	 t%          |d	                   t%          |d
                   }}|dk    rdS |dk    rd|cxk    rdk    rn ndS |dk    r|dk    rdS |dk    rd|cxk    rdk    rn ndS n# t          $ r Y nw xY wdS )a  Return True if base_url points to a local machine.

    Recognises loopback (``localhost``, ``127.0.0.0/8``, ``::1``),
    container-internal DNS names (``host.docker.internal`` et al.),
    RFC-1918 private ranges (``10/8``, ``172.16/12``, ``192.168/16``),
    link-local, and Tailscale CGNAT (``100.64.0.0/10``). Tailscale CGNAT
    is included so remote-but-trusted Ollama boxes reached over a
    Tailscale mesh get the same timeout auto-bumps as localhost Ollama.
    Fr   zhttp://r   Tc              3   B   K   | ]}                     |          V  d S r   )r   )r   rr   r   s     r   r   z$is_local_endpoint.<locals>.<genexpr>P  s/      
I
IV4==  
I
I
I
I
I
Ir   .   r   rj   
                  d   @      )r   r	   hostnamer   _LOCAL_HOSTSr   _CONTAINER_LOCAL_SUFFIXES	ipaddress
ip_address
is_privateis_loopbackis_link_localr   IPv4Address_TAILSCALE_CGNAT
ValueErrorrl   r   int)	r   r   urlr   addrpartsfirstsecondr   s	           @r   is_local_endpointr/  :  s5    %X..J u++**1G:1G1GC#$"   uu|t

I
I
I
I/H
I
I
III t  4t#D))? 	d. 	$2D 	4dI122 	t?O7O7O4    JJsOOE
5zzQ	aMM3uQx==6E{{t||f 2 2 2 2 2 2 2 2 2t||#t||f 3 3 3 3 3 3 3 3 3t 	 	 	D	5sK   : 
AA)C ,#C 
CC0E< =E< E< #E< <
F	F	c                     ddl }t          |           }|}|                    d          r
|dd         }t          |           }t	          |          }	 |                    d|          5 }	 |                    | d          }|j        dk    r	 ddd           d	S n# t          $ r Y nw xY w	 |                    | d
          }|j        dk    r8	 |	                                }	d|	v r	 ddd           dS n# t          $ r Y nw xY wn# t          $ r Y nw xY w	 |                    | d          }|j        dk    r|                    | d          }|j        dk    rd|j
        v r	 ddd           dS n# t          $ r Y nw xY w	 |                    | d          }|j        dk    r&|	                                }	d|	v r	 ddd           dS n# t          $ r Y nw xY wddd           n# 1 swxY w Y   n# t          $ r Y nw xY wdS )zDetect which local server is running at base_url by probing known endpoints.

    Returns one of: "ollama", "lm-studio", "vllm", "llamacpp", or None.
    r   Nr   g       @timeoutheaders/api/v1/models   	lm-studioz	/api/tagsmodelsrH   	/v1/props/propsdefault_generation_settingsllamacppz/versionversionvllm)httpxr   r   r   r   Clientgetstatus_coder   r   text)
r   r   r?  r   
server_urllmstudio_urlr4  clientr   r   s
             r   detect_local_server_typerG  r  sm   
 LLL$X..JJ5!! %_
(22LG$$G*\\#w\77 '	6JJ,>>>??=C''&'	 '	 '	 '	 '	 '	 '	 '	 (   

JJ*77788=C'' vvxx#t++#+#'	 '	 '	 '	 '	 '	 '	 '	  ,$      JJ*77788=C''

j#8#8#899A=C'',IQV,S,S%9'	 '	 '	 '	 '	 '	 '	 '	:    JJ*66677=C''6688D D((%K'	 '	 '	 '	 '	 '	 '	 '	L    M'	 '	 '	 '	 '	 '	 '	 '	 '	 '	 '	 '	 '	 '	 '	P     4s  G> +G2-#BG> G2
B,)G2+B,,G20#DC;-G> :D;
DDDDG2
DG2DG2AE;-G> :G2;
FG2FG2;GG> G2
G# G2"G##G2&G> 2G66G> 9G6:G> >
H
Hr   c              #      K   t          | t                    r2| V  |                                 D ]}t          |          E d {V  d S t          | t                    r| D ]}t          |          E d {V  d S d S r   )r   r   values_iter_nested_dictslist)r   nesteditems      r   rJ  rJ    s      % 0llnn 	2 	2F)&1111111111	2 	2	E4	 	  0 	0 	0D)$//////////0 0	0 	0r      逖 minimummaximumc                 $   	 t          | t                    rd S t          | t                    r(|                                                     dd          } t          |           }n# t          t          f$ r Y d S w xY w||cxk    r|k    rn n|S d S )Nr   r   )r   r   r   rm   replacer)  	TypeErrorr(  )r   rP  rQ  results       r   _coerce_reasonable_intrV    s    eT"" 	4eS!! 	3KKMM))#r22EUz"   tt&####G#####4s   A& AA& &A;:A;payloadkeys.c                     d |D             }t          |           D ]W}|                                D ]@\  }}t          |                                          |vr)t	          |          }||c c S AXd S )Nc                 6    h | ]}|                                 S r   rn   )r   r   s     r   	<setcomp>z%_extract_first_int.<locals>.<setcomp>  s     ***cciikk***r   )rJ  r   r   rn   rV  )rW  rX  keysetmappingr   r   coerceds          r   _extract_first_intr`    s    **T***F%g..  !--// 	 	JC3xx~~v--,U33G" #		 4r   c                 ,    t          | t                    S r   )r`  _CONTEXT_LENGTH_KEYSrW  s    r   _extract_context_lengthrd        g';<<<r   c                 ,    t          | t                    S r   )r`  _MAX_COMPLETION_KEYSrc  s    r   _extract_max_completion_tokensrh    re  r   c                 X  	 |                      d          }|                      d          }||Ri }|%t          t          |          dz  dz            |d<   |%t          t          |          dz  dz            |d<   |S ddd	d
dd}t          |           D ]}d |                                D             	t          	fd|                                D                       sNi }|                                D ]%\  }}|D ]}|	v r	|         dvr	|         ||<    n&|r|c S i S )Ninput_token_price_per_moutput_token_price_per_mi'  r   prompt
completion)rl  inputinput_cost_per_tokenprompt_token_cost)rm  outputoutput_cost_per_tokencompletion_token_cost)requestrequest_cost)
cache_readcached_promptinput_cache_readcache_read_cost_per_token)cache_writecache_creationinput_cache_writecache_write_cost_per_token)rl  rm  rt  rv  rz  c                 X    i | ]'\  }}t          |                                          |(S r   r   rn   r   s      r   r   z$_extract_pricing.<locals>.<dictcomp>  s.    PPP*#uc#hhnn&&PPPr   c              3   N   K   | ]}t          fd |D                       V   dS )c              3       K   | ]}|v V  	d S r   r   )r   aliasr   s     r   r   z-_extract_pricing.<locals>.<genexpr>.<genexpr>  s(      @@uu
*@@@@@@r   N)r   )r   aliasesr   s     r   r   z#_extract_pricing.<locals>.<genexpr>  s@      ccW3@@@@@@@@@ccccccr   >   Nr   )rA  r   floatrJ  r   r   rI  )
rW  novita_inputnovita_outputpricing	alias_mapr^  targetr  r  r   s
            @r   _extract_pricingr    s   ;;899LKK :;;M=#<"$# #E,$7$7&$@9$L M MGH$$'m(<(<v(E	(Q$R$RGL! S`.fk I &g..  PPPPP
ccccPYP`P`PbPbccccc 	"$(00 	 	OFG   J&&:e+<J+N+N&0&7GFOE 	NNN	Ir   r~   model_identryc                     || |<   d|v r4|                     dd          d         }|                     ||           d S d S )Nr   rj   )rl   
setdefault)r~   r  r  
bare_models       r   _add_model_aliasesr    sO    E(O
h^^C++A.
U+++++ r   Fforce_refreshc                 v   | s2t           r+t          j                    t          z
  t          k     rt           S | sJt	                      }|:|t          k     r/t                      }|r|a t          j                    |z
  at           S 	 t          j        t          dt                                }|
                                 |                                }i }|                    dg           D ]}|                    dd          }|                    dd          |                    d	i                               d
d          |                    d|          |                    di           d}t          |||           |                    dd          }	|	r|	|k    rt          ||	|           |a t          j                    at          |           t                              dt!          |                     |S # t"          $ r}
t                              d|
            t           rt           cY d}
~
S t                      }|rf|a t	                      }|*t          j                    t'          |t                    z
  ant          j                    t          z
  dz   at           cY d}
~
S i cY d}
~
S d}
~
ww xY w)z9Fetch model metadata from OpenRouter (cached for 1 hour).Nr  )r3  verifyr   idr   r   r   top_providerr   i   r   r  )r   r   r   r  canonical_slugz.Fetched metadata for %s models from OpenRouterz0Failed to fetch model metadata from OpenRouter: rj   )ru   r   rv   _MODEL_CACHE_TTLr   r   requestsrA  r   r   raise_for_statusr   r  r   r   r   r   r   warningmin)r  disk_age
disk_cacheresponser   r~   rf   r  r  	canonicalr   s              r   fetch_model_metadatar    s     %2 %	F`8`dt7t7t$$ -9;;H/?$?$?8::J -(2%-1Y[[8-C*,,&< 5rJbJdJdeee!!###}}XXfb)) 	< 	<Eyyr**H"')),<f"E"E).>2)F)F)J)JKbdh)i)i		&(33 99Y33	 E uh666		"2B77I <Y(22"5)U;;; %%)Y[["'...Es5zzRRR   M!MMNNN  	)(((((((466
 	)$.!=??H#-1Y[[3xIY;Z;Z-Z**-1Y[[;K-Ka-O*(((((((						s8   E9G< <
J8*J30J86A0J3&J8,J3-J83J8c                 ,   t          |           }|rt          |          ri S |sXt                              |          }t                              |d          }|!t          j                    |z
  t          k     r|S |g}|                    d          r|dd                             d          }n|dz   }|r||vr|	                    |           |rdd| ini }d}	t          |          r\	 t          ||          d	k    r+t          |          }
t          j        |
                    d          d
z   |dt                                }|                                 |                                }i }|                    dg           D ]y}t#          |t$                    s|                    d          p|                    d          }|sFd|                    d|          i}d}|                    dg           pg D ]y}t#          |t$                    s|                    di           }t#          |t$                    r|                    d          nd}t#          |t&                    r
|dk    r|} nz|||d<   t)          |          }|||d<   t+          |          }|r||d<   t-          |||           |                    d          }t#          |t.                    r|r||k    rt-          |||           {|t          |<   t          j                    t          |<   |S n# t0          $ r}|}	Y d}~nd}~ww xY w|D ]}|                    d          dz   }	 t          j        ||dt                                }|                                 |                                }i }|                    dg           D ]}t#          |t$                    s|                    d          }|s0d|                    d|          i}t3          |          }|||d<   t)          |          }|||d<   t+          |          }|r||d<   t-          |||           t5          d |                    dg           D                       }|r	 |                    d                              dd          }t                      }t          j        |dz   |d|          }|j        st          j        |dz   |d|          }|j        rh|                                }|                    di           } |                     d          }!|                    dd          }"|!r|"r|"|v r|!||"         d<   n# t0          $ r Y nw xY w|t          |<   t          j                    t          |<   |c S # t0          $ r}|}	Y d}~{d}~ww xY w|	rt:                              d ||	           i t          |<   t          j                    t          |<   i S )!zFetch model metadata from an OpenAI-compatible ``/models`` endpoint.

    This is used for explicit custom endpoints where hardcoded global model-name
    defaults are unreliable. Results are cached in memory per base URL.
    r   Nr   r1  r   r   r   r  r7  r5  r  r4  r3  r  r8  r   r  r   loaded_instancesconfigr   r   r  z/modelsr   c              3   r   K   | ]2}t          |t                    |                    d           dk    V  3dS )owned_byr<  N)r   r   rA  )r   ms     r   r   z0fetch_endpoint_model_metadata.<locals>.<genexpr>  sV        Jq$4G4Gj!!Z/     r   r   r9     r:  r;  r   model_aliasz1Failed to fetch model metadata from %s/models: %s)r   r   ry   rA  rz   r   _ENDPOINT_MODEL_CACHE_TTLr   r   appendr/  rG  r   r  r   r  r   r   r   r)  rh  r  r  r   r   rd  r   rS  okr   r   )#r   r   r  r   r	  	cached_at
candidates	alternater4  
last_errorrD  r  rW  r~   rf   r  r  r   instcfgctxr   r  alt_idexc	candidater*  is_llamacppbase_verify
props_resppropsgen_settingsr   r  s#                                      r   fetch_endpoint_model_metadatar  =  s    %X..J 0<< 	 /33J??7;;JJJ	49;;#:>W"W"WMJ5!! 'ssO**3//		&	 %Yj00)$$$8?G 3' 3 344RG&*J$$ 21	'
GDDDSS2:>>
#<%%c**-==#355	   ))+++"--//35$[[266 A AE%eT22 ! $yy//B599T??H# ! -3UYYvx5P5P,QE%)N %		*<b A A GR " ")$55 %$"hhx44;Ec4;P;PZcgg&6777VZ%c3// "C!GG-0N!E%12@./,J5,Q,Q),89N56.u55G 3+2i(&uh>>>"YYt__F!&#.. A6 Af>P>P*5&%@@@=B.z:BF)++3J?] T^  	 	 	JJJJJJ	   4 4	s##i/2	|C"MeMgMghhhH%%'''mmooG/1E VR00 ; ;!%..  99T?? )/681L1L(M!8!?!?!-.<E*+(Fu(M(M%(45JE12*511 /'.E)$"5(E::::    VR00    K  $++C0088CCD688G!)d[.@'[\el!m!m!mJ%= o%-\$/7\]fm%n%n%n
!} I * 1 1',yy1NPR'S'S , 0 0 9 9&+iir&B&B  I[ I[E5I5ICHE+./?@    D :?*:6>Bikk/
;LLL 	 	 	JJJJJJ	  bH*V`aaa13":.6:ikk'
3IsW   1I L3 3
M	=MM	*D/V7C#U>=V7>
VV7
V)V77
WW		Wc                    t          ||          }|                    |           }|sht          |          dk    r/t          t	          |                                                    }n&|                                D ]\  }}| |v s|| v r|} n|r,|                    d          }t          |t                    r|S dS )zDResolve context length from an endpoint's live ``/models`` metadata.r  rj   r   N)	r  rA  r   nextiterrI  r   r   r)  )rf   r   r   endpoint_metadatamatchedr   r  r   s           r    _resolve_endpoint_context_lengthr    s     6hPPP##E**G  !!Q&&4 1 8 8 : :;;<<GG/5577  
UC<<3%<<#GE $0  " %566nc** 	"!!4r   c                  (    ddl m}   |             dz  S )z8Return path to the persistent context length cache file.r   r|   zcontext_length_cache.yamlr   r|   s    r   _get_context_cache_pathr    s(    000000?:::r   c                  ^   t                      } |                                 si S 	 t          | d          5 }t          j        |          pi }ddd           n# 1 swxY w Y   |                    di           S # t          $ r'}t                              d|           i cY d}~S d}~ww xY w)z:Load the model+provider -> context_length cache from disk.r   r   Ncontext_lengthsz'Failed to load context length cache: %s)	r  r   r   yaml	safe_loadrA  r   r   r   )r   r   r   r   s       r   _load_context_cacher    s    "$$D;;== 	$))) 	+Q>!$$*D	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+xx)2...   >BBB						s@   A; AA; AA; !A"A; ;
B,B'!B,'B,c                    |  d| }t                      }|                    |          |k    rdS |||<   t                      }	 |j                            dd           t          |dd          5 }t          j        d|i|d	
           ddd           n# 1 swxY w Y   t          	                    d||d           dS # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)zPersist a discovered context length for a model+provider combo.

    Cache key is ``model@base_url`` so the same model name served from
    different providers can have different limits.
    @NTparentsexist_okwr   r   r  Fdefault_flow_stylez%Cached context length %s -> %s tokensr   z'Failed to save context length cache: %s)r  rA  r  parentmkdirr   r  dumpr   r  r   r   )rf   r   r  r   r~   r   r   r   s           r   r  r    sp    

X

C!!Eyy~~E#J"$$DC$666$g... 	O!I(%0!NNNN	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O;SV--PPPPP C C C>BBBBBBBBBCs<   .C 3BC BC  B!!C 
C4C//C4c                 V    |  d| }t                      }|                    |          S )zBLook up a previously discovered context length for model+provider.r  )r  rA  )rf   r   r   r~   s       r   get_cached_context_lengthr    s0    

X

C!!E99S>>r   c                    |  d| }t                      }||vrdS ||= t                      }	 |j                            dd           t	          |dd          5 }t          j        d|i|d	
           ddd           dS # 1 swxY w Y   dS # t          $ r'}t          	                    d||           Y d}~dS d}~ww xY w)zCDrop a stale cache entry so it gets re-resolved on the next lookup.r  NTr  r  r   r   r  Fr  z6Failed to invalidate context length cache entry %s: %s)
r  r  r  r  r   r  r  r   r   r   )rf   r   r   r~   r   r   r   s          r   r  r    sV   

X

C!!E
%c
"$$DW$666$g... 	O!I(%0!NNNN	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O W W WMsTUVVVVVVVVVWs;   .B B6B BB 
BB 
CB<<Ccurrent_lengthc                 .    t           D ]}|| k     r|c S dS )z@Return the next lower probe tier, or None if already at minimum.N)CONTEXT_PROBE_TIERS)r  tiers     r   get_next_probe_tierr  $  s/    #  .  KKK !4r   	error_msgc                     |                                  }g d}|D ]O}t          j        ||          }|r6t          |                    d                    }d|cxk    rdk    rn K|c S PdS )a?  Try to extract the actual context limit from an API error message.

    Many providers include the limit in their error text, e.g.:
      - "maximum context length is 32768 tokens"
      - "context_length_exceeded: 131072"
      - "Maximum context size 32768 exceeded"
      - "model's max context length is 65536"
    )z+max_model_len\s*(?:is\s*)?[:=(]?\s*(\d{4,})z2maximum model length\s*(?:is\s*)?[:=(]?\s*(\d{4,})zY(?:max(?:imum)?|limit)\s*(?:context\s*)?(?:length|size|window)?\s*(?:is|of|:)?\s*(\d{4,})z:context\s*(?:length|size|window)\s*(?:is|of|:)?\s*(\d{4,})z)(\d{4,})\s*(?:token)?\s*(?:context|limit)z">\s*(\d{4,})\s*(?:max|limit|token)z(\d{4,})\s*(?:max(?:imum)?)\brj   rN  rO  Nrn   researchr)  group)r  error_lowerpatternspatternrp   limits         r   parse_context_limit_from_errorr  ,  s     //##K  H   	';// 	A''Eu****
*****4r   current_context_lengthc                 <    t          |           }|dS ||k     r|S dS )u  Return a provider-reported lower context limit, if one is present.

    Context-overflow recovery must not invent a new model window size.  Some
    providers only say that the input exceeds the context window without
    reporting the actual maximum.  In that case callers should keep the
    configured context length and try compression only, rather than stepping
    down through guessed probe tiers (1M → 256K → 128K → ...).
    N)r  )r  r  parsed_limits      r   &get_context_length_from_provider_errorr  J  s3     2)<<Lt,,,4r   c                    |                                  }d|v od|v pd|v pd|v od|v pd|v od|v od|v pd|v }|sd	S t          j        d
|          }|r*t          |                    d                    }|dk    r|S g d}|D ]E}t          j        ||          }|r,t          |                    d                    }|dk    r|c S Ft          j        d|          }	t          j        d|          }
|	rr|
rpt          |	                    d                    t          |
                    d                    z
  t          |
                    d                    z
  }|dk    r|S t          j        d|          }t          j        d|          }|rY|rWt          |                    d                    }t          |                    d                    dz   dz  }||z
  }|dk    r|S t          j        d|          }|rO|rMt          |                    d                    t          |                    d                    z
  }|dk    r|S d	S )ul  Detect an "output cap too large" error and return how many output tokens are available.

    Background — two distinct context errors exist:
      1. "Prompt too long"  — the INPUT itself exceeds the context window.
           Fix: compress history, and only reduce context_length if the
           provider explicitly reports the actual lower limit.
      2. "max_tokens too large" — input is fine, but input + requested_output > window.
           Fix: reduce max_tokens (the output cap) for this call.
           Do NOT touch context_length — the window hasn't shrunk.

    Anthropic's API returns errors like:
      "max_tokens: 32768 > context_window: 200000 - input_tokens: 190000 = available_tokens: 10000"

    Returns the number of output tokens that would fit (e.g. 10000 above), or None if
    the error does not look like a max_tokens-too-large error.
    r   available_tokensavailable tokensin the outputmaximum context length	requestedoutput tokensrange of max_tokens should beNz9range of max_tokens should be\s*\[\s*\d+\s*,\s*(\d+)\s*\]rj   )zavailable_tokens[:\s]+(\d+)zavailable\s+tokens[:\s]+(\d+)z=\s*(\d+)\s*$zmaximum context length is (\d+)zK\((\d+)\s+of text input,\s*(\d+)\s+of tool input,\s*(\d+)\s+in the output\)   z'maximum context length is (\d+)\s*tokenz!prompt contains (\d+)\s*character   z2prompt contains (?:at least )?(\d+)\s*input tokensr  )r  r  is_output_cap_error_m_range_capr  r  rp   tokens_m_ctx_m_parts
_available
_m_ctx_tok_m_chars_ctx
_est_input_m_vllm_inputs                    r   (parse_available_output_tokens_from_errorr  ^  s   " //##K 	# 	U;.S2D2S
 	;& 	4$3 	!K/ 	+;&	+{*!0 	(;61 4  t yD H  8>>!$$%%199K  H   	';// 	Q((F{{ Y9;GGFyV H  ( a))Cq0A0A,B,BBSXYIZIZE[E[[
?? E{SSJy={KKH h :##A&&''(..++,,q0Q6
J&
?? I={ M  m ))!,,--M4G4G4J4J0K0KK
??4r   c                     |                                  }d|v pd|v pd|v }|sdS d|v p#d|v pd|v pd|v od	|v pd
|v od|v pd|v pd|v pd|v }|sdS d|v pd|v pd|v pd|v pd|v pd|v pd|v }| S )u5  Return True if a 400 is about the OUTPUT cap (max_tokens) being too large.

    This is the broader sibling of :func:`parse_available_output_tokens_from_error`:
    that function only returns a number when it can extract the available output
    budget from a *known* provider phrasing.  This one answers the cheaper
    yes/no question — "is this an output-cap error at all?" — across providers
    whose exact wording we may not yet parse a number from.

    Why this matters: an output-cap 400 is deterministic (every retry with the
    same ``max_tokens`` gets the identical rejection).  If such an error is
    misclassified as a context-overflow it gets routed into the compression
    loop, the compressor re-issues the call with the same oversized
    ``max_tokens``, the provider rejects it identically, and the session
    death-loops until "cannot compress further" (issue #55546, DashScope/Qwen:
    "Range of max_tokens should be [1, 65536]").  Compression cannot help an
    output-cap error — the input already fits.

    The signal: the error talks about ``max_tokens`` (or its aliases) as a
    cap/range/limit, and does NOT talk about the INPUT/prompt/context window
    being too long.  When both are present we defer to the context-overflow
    path (a real input overflow can also mention max_tokens).
    r   r   r   Fr  r  r  r  r  r  r  z	should bezless than or equalzmust bezprompt is too longzprompt too longzinput is too longzinput tokenzprompt lengthzprompt containszreduce the lengthr[  )r  r  mentions_output_paramoutput_cap_signalinput_overflow_signals        r   r  r    sX   . //##K 	# 	2+-	2"k1 
 ! u 	(;6 		$,		$,		$ {* 8(K7			$
 ;& /;.		$ +%		$  ;.		$ #   u 	+ 	.+	.+-	. K'	. k)		.
 +	. +-  %$$r   candidate_idlookup_modelc                 b    | |k    rdS d| v r"|                      dd          d         |k    rdS dS )a  Return True if *candidate_id* (from server) matches *lookup_model* (configured).

    Supports two forms:
    - Exact match:  "nvidia-nemotron-super-49b-v1" == "nvidia-nemotron-super-49b-v1"
    - Slug match:   "nvidia/nvidia-nemotron-super-49b-v1" matches "nvidia-nemotron-super-49b-v1"
                    (the part after the last "/" equals lookup_model)

    This covers LM Studio's native API which stores models as "publisher/slug"
    while users typically configure only the slug after the "local:" prefix.
    Tr   rj   F)r   )r  r	  s     r   _model_id_matchesr    sH     |##t
l|223::1=MMt5r   c                 @   ddl }t          |           }|                    d          }|                    d          r
|dd         }	 t	          ||          }n# t
          $ r Y dS w xY w|dk    rdS t          |          }	 |                    d|	          5 }|                    | d
d|i          }	|	j	        dk    r	 ddd           dS |	
                                }
|
                    dd          }d|v r|                    d          D ]s}d|v rm|                                                                }t          |          dk    r4	 t          |d                   c cddd           S # t           $ r Y ow xY wt|
                    di           }|                                D ]B\  }}d|v r9t%          |t          t&          f          rt          |          c cddd           S C	 ddd           n# 1 swxY w Y   n# t
          $ r Y nw xY wdS )ay  Query an Ollama server for the model's context length.

    Returns the model's maximum context from GGUF metadata via ``/api/show``,
    or the explicit ``num_ctx`` from the Modelfile if set.  Returns None if
    the server is unreachable or not Ollama.

    This is the value that should be passed as ``num_ctx`` in Ollama chat
    requests to override the default 2048.
    r   Nr   r   r1  r  rH         @r2  	/api/showr   r   r6  
parametersr   num_ctx
r  r   
model_infor   )r?  rt   r   r   rG  r   r   r@  postrB  r   rA  rl   rm   r   r)  r(  r   r   r  )rf   r   r   r?  r  rD  server_typer4  rF  respr   paramsliner,  r  r   r   s                    r   query_ollama_num_ctxr  ,  s    LLL'..J%%J5!! %_
.xIII   tthtG$$G\\#w\77 	&6;;*777vz>R;SSD3&&	& 	& 	& 	& 	& 	& 	& 	& 99;;D XXlB//FF"""LL.. % %D D(( $

 2 2 4 4u::??%'*59~~ 5 5	& 	& 	& 	& 	& 	& 	& 	& $. % % % $% ,33J(..00 & &
U#s**z%#u/N/N*u::%%-	& 	& 	& 	& 	& 	& 	& 	&(&)	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	&.    4s   	A 
A)(A)H )HH BHE7(H*H 7
FHFA#H'H 4H6H HH 	H
H 
HHc                 d   ddl }t          |           }|r|sdS 	 t          ||          dk    rdS n# t          $ r Y dS w xY w|                    d          }|                    d          r
|dd         }t          |          }	 |                    d|	          5 }|                    | d
d|i          }|j	        dk    r	 ddd           dS |
                                }	ddd           n# 1 swxY w Y   n# t          $ r Y dS w xY w|	                    d          }
t          |
t                    rt          d |
D                       rdS |
rdS |	                    d          }t          |t                    r+|D ](}dt!          |                                          v r dS )dS )a   Return True/False when Ollama ``/api/show`` reports vision support.

    Uses the ``capabilities`` field on Ollama 0.6.0+ and falls back to
    ``model_info.*.vision.block_count`` on older servers. Returns None when
    the server is unreachable, not Ollama, or the model is unknown.
    r   Nr  rH   r   r   r1  r  r2  r  r   r  r6  capabilitiesc              3   `   K   | ])}t          |                                          d k    V  *dS )visionNr  )r   caps     r   r   z/query_ollama_supports_vision.<locals>.<genexpr>  s7      <<s3xx~~8+<<<<<<r   TFr  zvision.block_count)r?  rt   rG  r   r   r   r   r@  r  rB  r   rA  r   rK  r   r   r   rn   )rf   r   r   r?  r  rD  r4  rF  r  r   capsr  r   s                r   query_ollama_supports_visionr   c  sn    LLL'..J X t#Hg>>>(JJ4 K   tt %%J5!! %_
G$$G\\#w\77 	6;;*777vz>R;SSD3&&	 	 	 	 	 	 	 	 99;;D		 	 	 	 	 	 	 	 	 	 	 	 	 	 	
    tt 88N##D$ <<t<<<<< 	4 	5,''J*d##  	 	C#s3xx~~'7'777tt 8 4sQ   3 
A AD )C5D C5)D 5C99D <C9=D 
DDc                    ddl }|                    d          }|                    d          r
|dd         }t          |          }	 |                    d|          5 }|                    | dd	| i
          }|j        dk    r	 ddd           dS |                                }|                    di           }	|		                                D ]J\  }
}d|
v rAt          |t          t          f          r%t          |          }|dk    r|c cddd           S K|                    dd          }d|v r|                    d          D ]|}d|v rv|                                                                }t          |          dk    r=	 t          |d                   }|dk    r|c cddd           S l# t           $ r Y xw xY w}ddd           n# 1 swxY w Y   n# t"          $ r Y nw xY wdS )u  Query an Ollama server's native ``/api/show`` for context length.

    Provider-agnostic: works against ANY Ollama-compatible server regardless
    of hostname — local Ollama, Ollama Cloud (``ollama.com``), custom Ollama
    hosting behind a reverse proxy, etc.  For non-Ollama servers the POST
    returns 404/405 quickly; the function handles errors gracefully.

    For hosted servers the GGUF ``model_info.*.context_length`` is the
    authoritative source: the user can't set their own ``num_ctx``, and the
    OpenAI-compat ``/v1/models`` endpoint correctly omits ``context_length``
    per the OpenAI schema.

    Resolution order for hosted Ollama:
      1. ``model_info.*.context_length`` — GGUF training max (authoritative)
      2. ``parameters`` → ``num_ctx`` — server-side Modelfile override
    The order is flipped vs ``query_ollama_num_ctx()`` because local users
    control ``num_ctx`` themselves; hosted users can't.
    r   Nr   r   r1  g      @r2  r  r   r  r6  r  r   rN  r  r   r  r  r  r   )r?  r   r   r   r@  r  rB  r   rA  r   r   r)  r  rl   rm   r   r(  r   )rf   r   r   r?  rD  r4  rF  r  r   r  r   r   r  r  r  r,  s                   r   _query_ollama_api_showr"    s   & LLL%%J5!! %_
G$$G\\#w\77 	%6;;*777vuo;NND3&&	% 	% 	% 	% 	% 	% 	% 	% 99;;D ,33J(..00 # #
U#s**z%#u/N/N*e**Cd{{"

	% 	% 	% 	% 	% 	% 	% 	%  XXlB//FF"""LL.. 	% 	%D D(( $

 2 2 4 4u::??%&)%)nn#&$;;+.JJ3	% 	% 	% 	% 	% 	% 	% 	%0 $/#- % % % $%5	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	%8    4s   	G#  )G	G# A;GG# A/GF:*G,G# 9G:
GGGGG# GG# GG# #
G0/G0c                 \    |                                  }|                    d          pd|v S )aB  Return True if the model name looks like a Kimi-family model.

    Catches ``kimi-k2.6``, ``kimi-k2.5``, ``kimi-k2-thinking``,
    ``moonshotai/Kimi-K2.6``, and similar variants.  Used as a guard
    against stale OpenRouter metadata that underreports these models
    as 32K context when they actually support 262K+.
    r9   rS   )rn   rk   )rf   rn   s     r   _model_name_suggests_kimir$    s/     KKMMEF##:zU'::r   c                 .    d|                                  v S )a  Return True if the model name looks like MiniMax M3.

    Catches ``MiniMax-M3``, ``minimax/minimax-m3``, and similar variants
    across surfaces (native MiniMax-M3, OpenRouter/Nous minimax/minimax-m3).
    Used as a guard against stale cache entries seeded by pre-catalog builds
    that resolved M3 via the generic ``minimax`` catch-all (204,800) before
    the ``minimax-m3`` (1M) entry existed in DEFAULT_CONTEXT_LENGTHS.
    r   r[  rf   s    r   _model_name_suggests_minimax_m3r'    s     5;;==((r   c                 .    d|                                  v S )az  Return True if the model name looks like a Grok 4.3 variant.

    Catches ``grok-4.3``, ``grok-4.3-latest``, and similar slugs.
    Used as a guard against stale cache entries seeded by pre-catalog builds
    that resolved grok-4.3 via the generic ``grok-4`` catch-all (256,000)
    before the ``grok-4.3`` (1M) entry was added to DEFAULT_CONTEXT_LENGTHS
    on 2026-05-15.
    r   r[  r&  s    r   _model_name_suggests_grok_4_3r)    s     &&r   c                 .   ddl }t          |           |                    d          f}|                                }t                              |          }|||d         z
  t          k     r|d         S t          | ||          }|r||ft          |<   |S )uD  Query a local server for the model's context length (short-TTL cached).

    The live-probe paths added for local endpoints (reconcile-on-hit and the
    pre-defaults step-7 probe) can fire this function several times in quick
    succession during one startup — banner display, ``/model`` switch,
    compressor ``update_model`` all resolve the same model. Each raw probe
    issues synchronous ``detect_local_server_type`` + query HTTP calls (bounded
    by the 3s httpx timeout), so an unreachable/slow local server would pay
    that cost repeatedly. A tiny in-process TTL cache collapses back-to-back
    probes for the same (model, base_url) into one network round-trip without
    persisting anything to disk (freshness across restarts is still handled by
    the reconcile logic, which probes again once the TTL expires).
    r   Nr   rj   r  )r   rt   r   	monotonicr   rA  _LOCAL_CTX_PROBE_TTL_SECONDS$_query_local_context_length_uncached)rf   r   r   _time	cache_keynowr	  rU  s           r   r  r    s     '..0D0DEI
//

C#''	22FsVAY2NNNay1%7SSSF  :-3SMy)Mr   c                 	   ddl }t          |           } |                    d          }|                    d          r
|dd         }t	          |          }t          |          }	 t          ||          }n# t          $ r d}Y nw xY w	 |                    d|          5 }|d	k    rL|	                    | d
d| i          }	|	j
        dk    r$|	                                }
|
                    dd          }d|v r|                    d          D ]s}d|v rm|                                                                }t          |          dk    r4	 t!          |d                   c cddd           S # t"          $ r Y ow xY wt|
                    di           }|                                D ]B\  }}d|v r9t'          |t           t(          f          rt!          |          c cddd           S C|dk    r|                    | d          }	|	j
        dk    r|	                                }
|
                    dg           D ]}t+          |                    dd          |           s$t+          |                    dd          |           r|                    dg           D ]j}|                    di           }|                    d          }|r;t'          |t           t(          f          rt!          |          c c cddd           S k n|                    | d|            }	|	j
        dk    r|	                                }
|
                    d          p)|
                    d          p|
                    d          }|r7t'          |t           t(          f          rt!          |          cddd           S |                    | d           }	|	j
        dk    r|	                                }
|
                    d!g           }|D ]}t+          |                    dd          |           rz|                    d          p)|                    d          p|                    d          }|r9t'          |t           t(          f          rt!          |          c cddd           S ddd           n# 1 swxY w Y   n# t          $ r Y nw xY wdS )"z4Query a local server for the model's context length.r   Nr   r   r1  r  r  r2  rH   r  r   r  r6  r  r   r  r  r  r   r  r   r7  r5  r8  r   r  r  r  z/v1/models/r   r   z
/v1/modelsr   )r?  rt   r   r   r   r   rG  r   r@  r  rB  r   rA  rl   rm   r   r)  r(  r   r   r  r  )rf   r   r   r?  rD  rE  r4  r  rF  r  r   r  r  r,  r  r   r   r  r  r  r  models_lists                         r   r-  r-    s0   LLL #5))E %%J5!! %_
(22LG$$G.xIII   E\\#w\77 B	,6h&&{{j#;#;#;65/{RR#s**99;;D "XXlB77F F**$*LL$6$6 - -D(D00(,

(:(:(<(<#&u::??%-/259~~(=(='B	, B	, B	, B	, B	, B	, B	, B	,( ,6 %- %- %-(,%- "&,!;!;J&0&6&6&8&8 . .
U+s22z%#u7V7V2#&u::--5B	, B	, B	, B	, B	, B	, B	, B	,B k))zz\"A"A"ABB#s**99;;D!XXh33 " ",QUU5"-=-=uEE "IZ[\[`[`aegi[j[jlqIrIr "().@"(E(E 4 4&*hhx&<&<&)gg.>&?&?#& !4:cC<+H+H !4+.s88OOOOYB	, B	, B	, B	, B	, B	, B	, B	,Z "E" ::????@@D3&&yy{{hh//g488<L3M3MgQUQYQYZfQgQg $:cC<88 $s88mB	, B	, B	, B	, B	, B	, B	, B	,t ::77788D3&&yy{{"hhvr22$ , ,A(tR%@@ ,eeO44f>N8O8OfSTSXSXYeSfSf ,:cC<#@#@ ,#&s88OOEB	, B	, B	, B	, B	, B	, B	, B	, B	, B	, B	, B	, B	, B	, B	, B	, B	, B	, B	, B	, B	, B	, B	,F     4s   'A9 9BBS #B2SE9*S,S 9
FSFA#S)S 6DSS B(S=S 
C"S,S 9S:S S

S S
S 
SSc                 .    |                      dd          S )zNormalize version separators for matching.

    Nous uses dashes: claude-opus-4-6, claude-sonnet-4-5
    OpenRouter uses dots: claude-opus-4.6, claude-sonnet-4.5
    Normalize both to dashes for comparison.
    r  -)rS  r&  s    r   _normalize_model_versionr5  w  s     ==c"""r   c                 r   |r|                     d          rdS 	 |                    d          }|                    d          r
|dd         }| d}|dd}t          j        ||d	t                      
          }|j        dk    rdS |                                }|                    dg           D ]O}|                    d          | k    r4|                    d          }	t          |	t                    r
|	dk    r|	c S Pn2# t          $ r%}
t                              d|
           Y d}
~
nd}
~
ww xY wdS )zQuery Anthropic's /v1/models endpoint for context length.

    Only works with regular ANTHROPIC_API_KEY (sk-ant-api*).
    OAuth tokens (sk-ant-oat*) from Claude Code return 401.
    z
sk-ant-oatNr   r   r1  z/v1/models?limit=1000z
2023-06-01)z	x-api-keyzanthropic-versionr  r  r6  r   r  r   r   z%Anthropic /v1/models query failed: %s)rk   r   r   r  rA  r   rB  r   r   r)  r   r   r   )rf   r   r   r  r*  r4  r  r   r  r  r   s              r   _query_anthropic_context_lengthr7    sz     g((66 tAs##== 	9D,,, !-
 
 |C"E]E_E_```s""4yy{{&"%% 	 	AuuT{{e##ee.//c3'' C!GGJJJ		
  A A A<a@@@@@@@@A4s%   A-D 
A8D D 
D4D//D4i& )
zgpt-5.1-codex-maxzgpt-5.1-codex-minizgpt-5.3-codexr   zgpt-5.2-codexr   r   r   zgpt-5.2r   _CODEX_OAUTH_CONTEXT_FALLBACK_codex_oauth_context_cacheg        _codex_oauth_context_cache_timeaccess_tokenc                 0   t          j                     }t          r|t          z
  t          k     rt          S 	 t	          j        ddd|  idt                                }|j        dk    r"t          	                    d|j                   i S |
                                }n4# t          $ r'}t          	                    d|           i cY d	}~S d	}~ww xY wt          |t                    r|                    d
g           ng }i }|D ]}t          |t                    s|                    d          }|                    d          }	t          |t                    r2t          |	t                    r|	dk    r|	||                                <   |r|a|a|S )ak  Probe the ChatGPT Codex /models endpoint for per-slug context windows.

    Codex OAuth imposes its own context limits that differ from the direct
    OpenAI API (e.g. gpt-5.5 is 1.05M on the API, 272K on Codex). The
    `context_window` field in each model entry is the authoritative source.

    Returns a ``{slug: context_window}`` dict. Empty on failure.
    zAhttps://chatgpt.com/backend-api/codex/models?client_version=1.0.0r   r   r  r  r6  zHCodex /models probe returned HTTP %s; falling back to hardcoded defaultszCodex /models probe failed: %sNr8  slugr   r   )r   r9  r:  _CODEX_OAUTH_CONTEXT_CACHE_TTLr  rA  r   rB  r   r   r   r   r   r   r   r)  rm   )
r;  r0  r  r   r  entriesrU  rM  r=  r  s
             r   "_fetch_codex_oauth_context_lengthsr@    s    )++C"*114RRR))|O$&>&>&>?+--	
 
 
 s""LLZ    Iyy{{   5s;;;						 )34(>(>Fdhhx$$$BGF ' '$%% 	xxhh'((dC   	'ZS%9%9 	'cAgg#&F4::<<  .%+"*-'Ms$   AB! B! !
C+CCCc                    t          |                                           }|sdS |ret          |          }||v r||         S |                                }|                                D ]!\  }}|                                |k    r|c S "|                                }t          t                                          d d          D ]\  }}||v r|c S dS )zResolve a Codex OAuth model's real context window.

    Prefers a live probe of chatgpt.com/backend-api/codex/models (when we
    have a bearer token), then falls back to ``_CODEX_OAUTH_CONTEXT_FALLBACK``.
    Nc                 ,    t          | d                   S Nr   r   xs    r   <lambda>z5_resolve_codex_oauth_context_length.<locals>.<lambda>  s    S1YY r   Tr   reverse)rt   rm   r@  rn   r   sortedr8  )rf   r;  
model_barelivemodel_lowerr=  r  s          r   #_resolve_codex_oauth_context_lengthrN    s    (..4466J t 1,??
## &&(( 	 	ID#zz||{**


 + ""$$K%++--3F3FPT    	c ;JJJ  4r   c                 8   |rt          | ||          }||dfS t                      }dt          dt          dt          t
                   fd}| |v r || ||                    }||dfS t          |                                           }|                                D ]\  }}	d	|v r|	                    d	d
          d
         n|}
|
                                |                                 k    s%t          |
                                          |k    r |||	          }||dfc S |                                 }|                                D ]\  }}	d	|v r|	                    d	d
          d
         n|}
|
                                |ft          |
                                          |ffD ]g\  }}|
                    |          rMt          |          t          |          k    s|t          |                   dv r |||	          }||dfc c S hdS )uM  Resolve Nous Portal model context length.

    Tries the live Nous inference endpoint first (authoritative), then falls
    back to OpenRouter metadata with suffix/version matching.

    Nous model IDs are bare after prefix-stripping (e.g. 'qwen3.6-plus',
    'claude-opus-4-6') while OpenRouter uses prefixed IDs (e.g.
    'qwen/qwen3.6-plus', 'anthropic/claude-opus-4.6').  Version
    normalization (dot↔dash) is applied to handle name drifts.

    Returns ``(context_length, source)`` where ``source`` is one of:
      - ``"portal"``    — live /v1/models response (authoritative)
      - ``"openrouter"`` — OpenRouter cache fallback (non-authoritative;
        callers must NOT persist this to the on-disk cache or a single
        portal blip will freeze the wrong value in forever)
      - ``""``           — could not resolve
    r  Nportalor_idr  r   c                     |                     d          }|d S |dk    r-t          |           rt                              d||            d S |S )Nr      z{Rejecting OpenRouter metadata context=%s for %r (Kimi-family underreport, Nous path); falling through to hardcoded defaults)rA  r$  r   r  )rQ  r  r  s      r   	_safe_ctxz/_resolve_nous_context_length.<locals>._safe_ctx5  sa    ii());4%<<5e<<<KK^U  
 4
r   rZ   r   rj   z-:.)Nr   )r  r  r   r   r   r)  r5  rn   r   rl   rk   r   )rf   r   r   
portal_ctxmetadatarT  r  r   rQ  r  barerM  r  querys                 r   _resolve_nous_context_lengthrY    sf   4  (5eXwWWW
!x''#%%H T hsm     ix//?$$)%006688J (( ) )u),u{{3""1%%5::<<5;;==((,DT,J,J,P,P,R,RV`,`,`)E5))CL((((++--K (( - -u),u{{3""1%%5"&**,,!<?WX\?]?]?c?c?e?egq>r s 	- 	-Iu##E** -I#e**,,	#e**0E0N0Niu--?,,,,,,	- 8r   config_context_lengthcustom_providersc                    |t          |t                    r|dk    r|S |pd                                                                dk    rT	 ddlm} ddlm} ddlm	}  | |            
                    d          pi |           }	|	
                    d          pi }
t          |

                    d	          pd                                          }t          |

                    d
          pd                                          }|rf|rd|                                dk    rL |||          }t          ||
                    dd          pd|
                    dd          pd|          S n,# t          $ r t                              dd           Y nw xY w|r.|r,| r*	 ddlm}  || ||          }|r|S n# t          $ r Y nw xY wt#          |           } |rt%          ||          sst'          | |          }|`|dk    r7|dk    r1t                              d| ||d           t+          | |           n#|dk    r?t-          |           r0t                              d| ||d           t+          | |           n|dk    r?t/          |           r0t                              d| ||d           t+          | |           n|dk    r?t1          |           r0t                              d| ||d           t+          | |           nTt3          |          dk    rt                              d| |           n$t5          |          rt7          | |||           S |S |d!k    s4|rTt9          |                              d"          r2t=          |d#          r"	 dd$lm }  ||           S # tB          $ r Y nw xY w|d%k    s|r;t=          |d&          r+tE          | |pd'|           }||rtG          | ||           |S tI          |          rOtK          |          s?tE          | ||           }||S tK          |          stM          | ||           }|#t%          ||          stG          | ||           |S t5          |          r=tO          | ||           }|r)|dk    r#t%          ||          stQ          | ||           |S t                              d(| |tR          d           |                                 }tU          tV          ,                                d) d*          D ],\  }}||v r#t                              d+|d| |           |c S -tR          S |d,k    s|r*t9          |          d-k    rt[          | |pd.|          }|r|S |}|r|d/v r|rt3          |          }|r|}|d0v r)	 dd1l.m/}  || |           }|r|S n# t          $ r Y nw xY w|dk    r6ta          | |pd|pd2          \  }}|r|r|d3k    rtG          | ||           |S |dk    r*tc          | |pd4          }|r|rtG          | ||           |S |d5k    r|rtE          | ||           }||S |r7tM          | ||           }|#t%          ||          stG          | ||           |S |d6k    rlte                      }|
                    |           }|rG|
                    d7          }t          |t                    r|dk    r|dk    rt-          |           s|S |rhdd8l3m4}   | ||           }|rTt/          |           rCtV          
                    d9          }!|!r'||!k     r!t                              d:|| |!d           |!}|S |sgte                      }| |v rU||          
                    d7tR                    }|dk    r,t-          |           rt                              d;||            n|S |rLt5          |          r=tO          | ||           }|r)|dk    r#t%          ||          stQ          | ||           |S |                                 }tU          tV          ,                                d< d*          D ]\  }}||v r|c S tR          S )=a2  Get the context length for a model.

    Resolution order:
    0. Explicit config override (model.context_length or custom_providers per-model)
    1. Persistent cache (previously discovered via probing).  Nous URLs
       bypass the cache here so step 5b can always reconcile against
       the authoritative portal /v1/models response.
    1b. AWS Bedrock static table (must precede custom-endpoint probe)
    2. Active endpoint metadata (/models for explicit custom endpoints)
    3. Local server query (for local endpoints)
    4. Anthropic /v1/models API (API-key users only, not OAuth)
    5. Provider-aware lookups (before generic OpenRouter cache):
       a. Copilot live /models API
       b. Nous: live /v1/models probe first (authoritative), then OR
          cache fallback with suffix/version normalisation.  Only
          portal-derived values are persisted to disk.
       c. Codex OAuth /models probe
       d. GMI /models endpoint
       e. Ollama native /api/show probe (any base_url, provider-agnostic)
       f. models.dev registry lookup (with :cloud/-cloud suffix fallback)
    6. OpenRouter live API metadata (Kimi-family 32k guard)
    7. Local server query (before hardcoded defaults for local endpoints)
    8. Hardcoded defaults (broad family patterns, longest-key-first)
    9. Default fallback (256K)Nr   r   moa)load_config)resolve_moa_preset)resolve_runtime_provider
aggregatorr   rf   )r  target_modelr   r   )r   r   r   z/MoA aggregator context-length resolution failedT)exc_info)"get_custom_provider_context_length)rf   r   r[  ra   r   zaDropping stale Codex cache entry %s@%s -> %s (pre-fix value); re-resolving via live /models prober   rS  ziDropping stale Kimi cache entry %s@%s -> %s (OpenRouter underreport); re-resolving via hardcoded defaultsr   zjDropping stale MiniMax-M3 cache entry %s@%s -> %s (pre-catalog value); re-resolving via hardcoded defaultsr   zhDropping stale Grok-4.3 cache entry %s@%s -> %s (pre-catalog value); re-resolving via hardcoded defaultsr;   z@Bypassing persistent cache for %s@%s (Nous portal authoritative)r  bedrockzbedrock-runtime.zamazonaws.com)get_bedrock_context_lengthrF   r   zhttps://api.novita.ai/openai/v1u   Could not detect context length for model %r at %s — defaulting to %s tokens (probe-down). Set model.context_length in config.yaml to override.c                 ,    t          | d                   S rC  rD  rE  s    r   rG  z*get_model_context_length.<locals>.<lambda>C  s    c!A$ii r   rH  zUUsing hardcoded context length %s for model %r (custom endpoint, catalog match on %r)rX   r   zhttps://api.anthropic.com>   rB   rZ   >   rL   r]   rb   )get_copilot_model_context)r   r   rP  )r;  r2   rZ   r   )lookup_models_dev_contextr   z[Rejecting models.dev context=%s for %r (MiniMax-M3 underreport); using hardcoded default %szpRejecting OpenRouter metadata context=%s for %r (Kimi-family underreport); falling through to hardcoded defaultsc                 ,    t          | d                   S rC  rD  rE  s    r   rG  z*get_model_context_length.<locals>.<lambda>  s    s1Q4yy r   )5r   r)  rm   rn   hermes_cli.configr^  hermes_cli.moa_configr_  hermes_cli.runtime_providerr`  rA  r   get_model_context_lengthr   r   r   rd  rt   r  r  r  r  r$  r'  r)  r   r/  r  r   rk   r   agent.bedrock_adapterrf  ImportErrorr  r  r   r   r"  r  r  DEFAULT_FALLBACK_CONTEXTrJ  DEFAULT_CONTEXT_LENGTHSr   r7  hermes_cli.modelsrh  rY  rN  r  agent.models_devri  )"rf   r   r   rZ  r   r[  r^  r_  r`  presetaggagg_provider	agg_modelrtrd  cp_ctxr	  rf  r  r   	local_ctxrM  default_modelr  effective_providerinferredrh  source	codex_ctxrV  r  or_ctxri  catalogs"                                     r   rn  rn  ^  s   B (Z8Ms-S-S(XmpqXqXq$$ 	B%%''500	[555555@@@@@@LLLLLL''(9(9%(@(@(FBNNF**\**0bCswwz228b99??AALCGGG,,23399;;I \ l.@.@.B.Be.K.K--S\]]]/VVJ339rFF9b117R)	     	[ 	[ 	[LLJUYLZZZZZ	[  H  
	LLLLLL77!!1  F
   	 	 	D	 #5))E  E6xJJ E*5(;; >))f.?.?:8]]  
 2%BBBB5%>u%E%E:8]]  
 2%BBBB 7""'Fu'M'M":8]]  
 2%BBBB 7""'DU'K'K":8]]  
 2%BBBB *(33v==V8    %X.. Ax     9 h''223EFF  "(O<< 
	HHHHHH--e444 	 	 	D	 8-B8_-]-].uh6cBcmtuuu? :#E8S999J 8$$ .,-H-R-R .,9%SZ[[[%!!*844 *	, )'JJJC5hII >'x===
 ** %7xQXYYY	 %Q9(HMM V9%9UUU$$KK. x$<!@!@	    ++--K)/'--//''* * * " "%v
 !K//KKA!um  
 "MMM 0 ,+ ; &x004GGG-eX5\A\^eff 	J " .!37O!O!O 	./99H .%-" III	CCCCCC++E7CCCC 
 	 	 	D	 V##2HNGMr
 
 
V  		  :Fh..#E8S999J^++ 8GMWYZZZ	 	 @#E8Y???U""x" /uhPPP?J  $UHgFFF?1(HEE :#E8S999J \))'))U## 	YY/00F &#&& 6A::%$=e$D$D >>>>>>''(:EBB 	 /u55 "155lCC "sW}}KKOUwNN  
 "CJ  '))He_(()9;STTF#<U#C#CWE    
  %h// /xQQQ	 	Q1(HEE N1%9MMM ++--K!'%%''-@-@$" " "  v K''MMM ( $#sI   D%E5 5&FF(G 
GG"N3 3
O ?O 9W 
WWc           	      \   K   ddl }|                    t          | |||||           d{V S )uV  Async variant of get_model_context_length.

    Offloads the entire synchronous resolution chain (which contains
    blocking HTTP calls via ``requests``) to a background thread so it
    does not freeze the asyncio event loop and cause Discord heartbeat
    timeouts.

    Shares all logic with the sync version — no code duplication.
    r   N)r   r   rZ  r   r[  )asyncio	to_threadrn  )rf   r   r   rZ  r   r[  r  s          r   get_model_context_length_asyncr    sa      " NNN"" 3) #         r   rC  c                 4    | sdS t          |           dz   dz  S )a  Rough token estimate (~4 chars/token) for pre-flight checks.

    Uses ceiling division so short texts (1-3 chars) never estimate as
    0 tokens, which would cause the compressor and pre-flight checks to
    systematically undercount when many short tool results are present.
    r   r  r  rD  )rC  s    r   estimate_tokens_roughr  	  s&      qIIMar   messagesc                 x    d}d}d}| D ]'}|t          |          z  }|t          ||          z  }(|dz   dz  |z   S )ue  Rough token estimate for a message list (pre-flight only).

    Image parts (base64 PNG/JPEG) are counted as a flat ~1500 tokens per
    image — the Anthropic pricing model — instead of counting raw base64
    character length. Without this, a single ~1MB screenshot would be
    estimated at ~250K tokens and trigger premature context compression.
    i  r   r  r  )_estimate_message_chars_count_image_tokens)r  _IMAGE_TOKEN_COSTtotal_charsimage_tokensmsgs        r   estimate_messages_tokens_roughr  	  sd     KL D D.s333+C1BCCC1_"l22r   r  cost_per_imagec                 
   d}t          | t                    r|                     d          nd}t          |t                    r9|D ]6}t          |t                    s|                    d          }|dv r|dz  }7t          | t                    r|                     d          nd}t          |t                    r8|D ]5}t          |t                    r|                    d          dk    r|dz  }6t          |t                    ru|                    d	          r`|                    d          }t          |t                    r6|D ]3}t          |t                    r|                    d          d
v r|dz  }4||z  S )zECount image-like content parts in a message; return their token cost.r   contentNtype>   image	image_urlinput_imagerj   _anthropic_content_blocksr  _multimodal>   r  r  )r   r   rA  rK  )r  r  countr  partptypestashedinners           r   r  r  *	  s   E$.sD$9$9Ccggi   tG'4    	 	DdD)) HHV$$E===
6@d6K6KUcgg1222QUG'4    	 	D$%% $((6*:*:g*E*E
'4   W[[%?%? I&&eT"" 	  dD)) dhhv.>.>BX.X.XQJE>!!r   c                    t          | t                    st          t          |                     S i }|                                 D ]\  }}|dk    r|dk    rt          |t
                    rg }|D ]}t          |t                    rY|                    d          dv r,|                    |                    d          dd           Z|                    |           p|                    |           |||<   t          |t                    r/|                    d          r|                    dd	          ||<   |||<   |||<   
t          t          |                    S )
zChar count for token estimation, excluding base64 image data.

    Base64 images are counted via `_count_image_tokens` instead; including
    their raw chars here would massively overestimate token usage.
    r  r  r  >   r  r  r  z
[stripped])r  r  r  text_summaryr   )r   r   r   r   r   rK  rA  r  )r  shadowkvcleanedr  s         r   r  r  D	  sv    c4   3s88}}F		  1+++	>>!T""  - -D!$-- -88F++/TTT#NNDHHV4D4D|+\+\]]]]#NN40000t,,,,#q		At$$ })=)= EE."55q		q		F1IIs6{{r   )system_prompttoolsr  r  c                    d}|r|t          |          dz   dz  z  }| r|t          |           z  }|r%|t          t          |                    dz   dz  z  }|S )u  Rough token estimate for a full chat-completions request.

    Includes the major payload buckets Hermes sends to providers:
    system prompt, conversation messages, and tool schemas.  With 50+
    tools enabled, schemas alone can add 20-30K tokens — a significant
    blind spot when only counting messages. Image content is counted
    at a flat per-image cost (see estimate_messages_tokens_rough).
    r   r  r  )r   r  r   )r  r  r  totals       r   estimate_request_tokens_roughr  e	  su     E /#m$$q(Q.. :/999 ,#c%jj//A%!++Lr   )r   )rN  rO  )F)r   F)r   r   )r   r   Nr   N)__doc__r!  r   loggingr   r  r   pathlibr   typingr   r   r   r   r   urllib.parser	   r  r  utilsr
   r   r   r   r   	getLogger__name__r   r   r   r   	frozensetre   __annotations__compile
IGNORECASEro   IPv4Networkr'  rt   ru   rv   r  rw   rx   r  ry   rz   r  r   r   r   r   r  rq  r  r,  r   tuplerr  r   r   rb  rg  r  r   r   r   r   r   r   	providersr   _list_providers_ppget_hostname_hostr   r   r   r   r   r  r)  r  r  r/  rG  rJ  rV  r`  rd  rh  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r   r"  r$  r'  r)  r  r-  r5  r7  r8  r9  r:  r>  r@  rN  rY  rK  rn  r  r  r  r  r  r  r   r   r   <module>r     s           				 				        3 3 3 3 3 3 3 3 3 3 3 3 3 3 ! ! ! ! ! !   M M M M M M M M M M 2 2 2 2 2 2		8	$	$$*    * &/Y 0 0 0 & & IcN   4 !bjLM   )9(99 # #    & 46 tCc3h/0 5 5 5$% E % % %46 S$sCx.01 6 6 6%& U & & & GI S$sDcN/B*C%C D I I I8: #T#u*%5 : : : J J J J J    c4S>.A)B    $
S$sDcN/B*C 
S 
S 
S 
S 
S     /q1 
     $ -/ UE\* / / /A
 gA GA wA wA wA wA wA A wA A  f!A. w/A0 F1A2 F3A4 w5AB 6CAD FEA AF VGAH wIAJ VKAN gOAR vSAT fUAV 6WAX vYAZ T[Aj ykAl mAn YoAp qAr sAv VwA| G}A~ A A A@ 6AAB FCAN 'OAP vQA^ y_A` 
6aAv VwAx &yAz f{A| T}A~ 7A@ AAB CAD fEAF fGAH fIAJ FKA A AN FOAV 6WAZ [A^ v_Ab cAf fgAh FiAj  kAl FmAn FoAp "6qAr fsAt uAv 7wAx WyAz {A| F}A A~ AA A A `! T# T$ T T T T$   < 0# 0# 0 0 0 00 03 0S#X 0 0 0 0<c <d < < < <H# H$ H H H H
*$h*$8*$ *$ 	*$
 *$ }*$ '*$ M*$ i*$ y*$ G*$ 9*$ i*$ "9*$ l*$  \!*$" (#*$ *$$ %f%*$& 
'*$( Y)*$0 )1*$2 	3*$> $Y?*$@ A*$B =C*$D E*$F G*$H (I*$J hK*$L 5M*$N XO*$P  2Q*$R .S*$ *$ $sCx. * * *\	;;;;;;   / /  "" 	/U"222&)hU#/  	 	 	D	s x}    $C C    :# :$ : : : :"S "C "D " " " "555 5 
	5 5 5 5( 	" """ " 	"
 	" " " "J5 5 5 5 5 5p; ;s ;S ;(3- ; ; ; ;|0c 0 0 0 0 #  S ZbcfZg    	S#X 	eCHo 	(SV- 	 	 	 	=T#s(^ = = = = ==DcN =x} = = = =d38n c3h    B,d3S#X#67 ,3 ,tTWY\T\~ ,bf , , , ,6 6 6c4S>>Q9R 6 6 6 6v M MMM M 
#tCH~
	M M M Mf    c]	   .; ; ; ; ;T#s(^    Cs Cc C3 C4 C C C C*S C HSM    WS WC WD W W W W      c hsm    < c]   (x x x x x xv=%3 =%4 =% =% =% =%@C s t    &4 4 4s 4S 4(SV- 4 4 4 4n/ / /s /S /RZ[_R` / / / /d: :# : :s :HUXM : : : :z	;S 	;T 	; 	; 	; 	;	)3 	)4 	) 	) 	) 	)	' 	' 	' 	' 	' 	' s c C QYZ]Q^    D\ \ \s \S \ZbcfZg \ \ \ \~#C #C # # # #3 #  PXY\P]    P !! #1 1 tCH~   & .0 DcN / / /),  , , ,!% 0S 0T#s(^ 0 0 0 0h %' !c]   F G GGG G 8C=#	G G G GX (,$(O$ O$O$O$ O$ :	O$
 O$ TkO$ 	O$ O$ O$ O$h (,$(   :	
  Tk 	   :	  	  	  	  	  	 3T$sCx.-A 3c 3 3 3 3""T#s(^ "S "S " " " "4c3h C    H ,0	  4S>"  Dc3h()	
 	     s   7L< <MM