
    .cjC                       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	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 Z0eeeeeeef         f         f         e#d<   i Z1eee,f         e#d<   dZ2dZ3i Z4eee5f         e#d<   de
fdZ6dee,         fdZ7deeeeef         f         fdZ8deeeeef         f         ddfdZ9g dZ:e:d         Z;d Z<d!Z=i Z>ee5e5f         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/d0d1d2d3d2d4d2d5d2d6d7i d8d7d9d2d:d;d<d;d=d7d>d?d@d;dAdBdCdDdEdDdFdDdGdHdIdJdKd$dLd$dMd$dNd$i dOd;dPdHdQdBdRdBdSd$dTdUdVdUdWdHdXd$dYdZd[dBd\d]d^d0d_d`dadDdbdDdcdJi dddedfdedgd`dhd$didDdjdHdkdHdldHdmdBdndUdodUdpdHdqdrdsdtdudUdvdUdwdHi dxdUdydUdzdHd{dHd|drd}dUd~dUddUddZddUddBddBddBddUddUdd]Z?dZ@dedefdZAdZBdZCdZDdZEdedefdZFd3dedeeef         fdZGdedefdZHdedefdZIi ddddddddddddddddddddddddYddddddddddAi ddddOddddddddddddddddddddddēddƓddȓddʓZJeeef         e#d<   	 ddlKmLZM  eM            D ]&ZNeNO                                ZPePrePeJvr
eNjQ        eJeP<   'n# eR$ r Y nw xY wdedee         fd̈́ZSdedefd΄ZTdedefdτZUdededeeV         fdЄZWdededefd҄ZXdededeVddfdԄZY	 d3dededeVdedeVf
dքZZdedefdׄZ[dedefdلZ\d3dededee         fdڄZ]defd܄Z^d4dedeVdeVdeeV         fdZ_deeef         de5edf         deeV         fdZ`deeef         deeV         fdZadeeef         deeV         fdZbdeeef         deeef         fdZcdeeeeef         f         dedeeef         ddfdZdd5dedeeeeef         f         fdZe	 	 d6dedededeeeeef         f         fdZf	 d3dedededeeV         fdZgde
fdZhdeeeVf         fdZidededefdZjdededeVddfdZkdededeeV         fdZldededdfdZmdeVdeeV         fdZndedeeV         fdZodedeVdeeV         fdZpdedeeV         fdZqdedefdZrd ededefdZsd3dedededeeV         fdZtd3dedededee         fdZud3dedededeeV         fdZvd3dedededeeV         fdZwdedefdZxdedefdZydedefd	Zzd3dedededeeV         fd
Z{d3dedededeeV         fdZ|dedefdZ}dedededeeV         fdZ~dddd;ddddddddddZeeeVf         e#d<   i aeeeeeeVf         e,f         f         e#d<   dZdedefdZdedee         fdZdedeeeeVf         ef         fdZdedeeeVf         fdZ	 d3dededeeeV         ef         fdZ	 d3dededeeV         fdZ	 	 d7dedededeeeV         ef         fdZ	 	 	 	 	 d8dedededeVdz  dededz  deVfdZ	 	 	 	 	 d8dedededeVdz  dededz  deVfdZdedefdZ ej$        d           Zd!edeVfd"Zd#eeeef                  deVfd$Zd%eeef         d&eVdeVfd'Zd%eeef         deVfd(Zd%eeef         deVfd)Zddd*d#eeeef                  d+ed,eeeeef                           deVfd-Zi ZeeVeeVeeeVf         f         e#d.<   d/Zd0edefd1Zd,eeeef                  deVfd2ZdS (9  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     6/home/ice/.hermes/hermes-agent/agent/model_metadata.py_resolve_requests_verifyr      sO     O  i   	27>>#&& 	JJJ4    >K   x-aix.aiz-aiz.aikimi-cnarcee-ai	deep-seek	gmi-cloud	novita-ai
deep-infra
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	deepinfra
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lowerrg   _OLLAMA_TAG_PATTERNmatch)rh   prefixsuffixprefix_lowers       r   _strip_provider_prefixrv   Y   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,  g      @_endpoint_probe_path_cachec                  .    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      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      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   ,rj   )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-sonnet-5z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.6rB   i@ gpt-5.6-lunai gpt-5.6-terragpt-5.6-solgpt-5.5zgpt-5.4-nanoi gpt-5.4-minigpt-5.4gpt-5.3-codex-sparkr   zgpt-5.1-chatgpt-5zgpt-4.1i zgpt-4rD      zgemma-4r   gemma4zgemma-4-31bzgemma-3i   gemmai    zdeepseek-v4-prozdeepseek-v4-flashzdeepseek-chatzdeepseek-reasonerrQ   llamazqwen3.6-pluszqwen3.7-pluszqwen3-coder-pluszqwen3-coderi   z	qwen3-maxr=   
minimax-m3rN      zglm-5.2r2   i  zgrok-composerzgrok-build-latesti  z
grok-buildzgrok-code-fastzgrok-2-visionzgrok-4-fasti z	grok-4.20grok-4.5grok-4.3zgrok-4zgrok-3zgrok-2r8   kimi-k3r:   zsolar-open2z
solar-pro3z
solar-pro2i   z
solar-mini   zhy3-previewhy3rU   trinityelephantzQwen/Qwen3.5-397B-A17BzQwen/Qwen3.5-35B-A3Bzdeepseek-ai/DeepSeek-V3.2z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-omnizmimo-v2-flashzzai-org/GLM-5)zgrok-3-minizgrok-4.20-multi-agentr   r   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)/rl   c              3   B   K   | ]}                     |          V  d S N)rm   )r   rs   names     r   	<genexpr>z1grok_supports_reasoning_effort.<locals>.<genexpr>  s/      SS6tv&&SSSSSSr   )ro   rp   rsplitany_GROK_EFFORT_CAPABLE_PREFIXES)rh   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   )ro   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   ro   )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.comrY   zapi.z.air6   zopen.bigmodel.cnzapi.moonshot.air`   zapi.moonshot.cnre   api.kimi.comzapi.stepfun.airO   zapi.stepfun.comzapi.arcee.air>   zapi.minimaxzdashscope.aliyuncs.comrK   zdashscope-intl.aliyuncs.comzportal.qwen.air^   r   r\   z!generativelanguage.googleapis.comzinference-api.nousresearch.comr<   zapi.deepseek.comzapi.githubcopilot.comrM   z.githubcopilot.comzmodels.github.aizmodels.inference.ai.azure.comzapi.fireworks.ai	fireworkszopencode.aira   zapi.x.air5   zintegrate.api.nvidia.comrH   zapi.xiaomimimo.comrJ   zxiaomimimo.comzapi.gmi-serving.comr3   api.novita.airG   ztokenhub.tencentmaas.comrf   z
ollama.comrb   _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	   netlocrp   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   rootrt   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   c                    t          |          }	 t          |          }|j        }n# t          $ r Y dS w xY w|j                                        dk    r|j        pd                                dk    rf|dv rb|j        [|j        T|j	        
                    d          dv r8|j        s1|j        s*|                                                                 dv rd	S dS )
ac  Return metadata confirmed only for the Kimi Coding endpoint.

    Kimi Coding serves K3 under the bare slug ``k3``, but users may also
    configure or select the public-facing aliases ``kimi-k3`` and
    ``kimi-k3-cot``. Only canonical ``https://api.kimi.com/coding`` endpoints
    (legacy Moonshot keys do not serve K3) get the 1 Mi context window.
    Nhttpsr   r   )Ni  r   >   /coding
/coding/v1>   kimi-k3-cotk3r   r   )r   r	   port
ValueErrorschemerp   hostnameusernamepasswordr   r   queryfragmentro   )rh   r   r   r   r  s        r   _endpoint_scoped_context_lengthr  (  s     %X..J*%%{   tt 	((_"))++~==KO#O#Ks##'@@@ A A KKMM!!%EEEy4s   ( 
66r  c                 V    |pd                                                                 dv 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.

    Codex OAuth excludes caching because its context window is account- and
    entitlement-specific metadata supplied by the authenticated /models
    endpoint. A fallback value written after a transient probe failure must
    not prevent a later live probe from observing an updated allocation.
    r   >   lmstudiorc   )ro   rp   )r   r  s     r   _skip_persistent_context_cacher  E  s,     N!!##))++/KKKr   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)rh   r   r  s      r   !_maybe_cache_local_context_lengthr#  S  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#  )rh   r   r$  r   live_ctxs        r   &_reconcile_local_cached_context_lengthr+  c  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   rt   r  s     r   r   z$is_local_endpoint.<locals>.<genexpr>  s/      
I
IV4==  
I
I
I
I
I
Ir   .   r   rl   
                  d   @      )r   r	   r  r   _LOCAL_HOSTSr   _CONTAINER_LOCAL_SUFFIXES	ipaddress
ip_address
is_privateis_loopbackis_link_localr   IPv4Address_TAILSCALE_CGNATr  rn   r  int)	r   r   urlr   addrpartsfirstsecondr  s	           @r   is_local_endpointrH    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	rC  c                 :    | s| S t          j        dd| d          S )a`  Rewrite a ``localhost`` HOST to ``127.0.0.1`` in a probe URL.

    On Windows dual-stack machines, httpx resolves ``localhost`` to ``::1``
    first and pays a ~2s IPv6 connect timeout before falling back to IPv4
    when the local server only listens on IPv4 (LM Studio, Ollama defaults).
    Probing the IPv4 loopback directly skips that penalty.

    Only the URL's own host component is rewritten (anchored at the scheme),
    so a non-localhost URL whose path or query merely embeds the substring
    ``http://localhost...`` (e.g. ``?upstream=http://localhost:11434``)
    passes through untouched.
    z^(https?://)localhost(?=[:/]|$)z\g<1>127.0.0.1rl   )count)resub)rC  s    r   _localhost_to_ipv4rM    s6      
6*	   r   c                    ddl }t          |           }t          |          }|}|                    d          r
|dd         }t	          |          }t
                              |          }|-t          j                    |d         z
  t          k     r|d         S t          |          }d}	 |                    d|          5 }		 |	                    | d          }
|
j        d	k    rd
}n# t          $ r Y nw xY w|a	 |	                    | d          }
|
j        d	k    r,	 |
                                }d|v rd}n# t          $ r Y nw xY wn# t          $ r Y nw xY w|c	 |	                    | d          }
|
j        d	k    r|	                    | d          }
|
j        d	k    rd|
j        v rd}n# t          $ r Y nw xY w|O	 |	                    | d          }
|
j        d	k    r|
                                }d|v rd}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||t          j                    ft
          |<   |S )as  Detect which local server is running at base_url by probing known endpoints.

    Returns one of: "ollama", "lm-studio", "vllm", "llamacpp", or None.

    The result is cached for the lifetime of the process so that repeated
    calls (e.g. every 5-minute metadata refresh) never re-run the waterfall
    and never spray 404s at endpoints the server does not expose.
    r   Nr  rl   g       @timeoutheaders/api/v1/models   	lm-studioz	/api/tagsmodelsrI   	/v1/props/propsdefault_generation_settingsllamacppz/versionversionvllm)httpxr   rM  r  r
  r}   getr   	monotonic_ENDPOINT_PROBE_TTL_SECONDSr   Clientstatus_coder   r   text)r   r   r]  r   
server_urllmstudio_urlr$  rR  resultclientr   r   s               r   detect_local_server_typerh    sI    LLL$X..J
 $J//JJ5!! %_
(44L'++J77Ft~//&);?ZZZayG$$G F-\\#w\77 *	6JJ,>>>??=C''(F   ~


j#;#;#;<<A}++!#$6688D'4//)1( ! ! ! D!    D~

j#;#;#;<<A}++"JJ*'<'<'<==}++0MQRQW0W0W!+    D~

j#:#:#:;;A}++ vvxx$,,%+F    DS*	 *	 *	 *	 *	 *	 *	 *	 *	 *	 *	 *	 *	 *	 *	V     28$.:J:J1K":.Ms   .H. H"%C-,H"-
C:7H"9C::H" #E$D?>E?
E	EEEH"
EH"EH"#AF54H"5
G?H"GH"=HH"
HH"HH"H. "H&&H. )H&*H. .
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   rk  rk  )  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   ro   replacerB  	TypeErrorr  )r   rq  rr  rf  s       r   _coerce_reasonable_intrv  3  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   rp   )r   r   s     r   	<setcomp>z%_extract_first_int.<locals>.<setcomp>B  s     ***cciikk***r   )rk  r   r   rp   rv  )rw  rx  keysetmappingr   r   coerceds          r   _extract_first_intr  A  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_lengthr  M      g';<<<r   c                 ,    t          | t                    S r   )r  _MAX_COMPLETION_KEYSr  s    r   _extract_max_completion_tokensr  Q  r  r   c                    |                      d          }|                      d          }||Ri }|%t          t          |          dz  dz            |d<   |%t          t          |          dz  dz            |d<   |S t          |                      d          t                    r|                      d          nd }|r|                     d          nd t          t                    rt          fd	d
D                       ri }                     d          (t          t          d                   dz            |d<                        d          (t          t          d                   dz            |d<                        d          (t          t          d                   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metadatapricingc              3       K   | ]}|v V  	d S r   r   )r   kdeepinfra_pricings     r   r   z#_extract_pricing.<locals>.<genexpr>f  s:       3 3#$3 3 3 3 3 3r   )input_tokensoutput_tokenscache_read_tokensr  r  r  
cache_read)r  inputinput_cost_per_tokenprompt_token_cost)r  outputoutput_cost_per_tokencompletion_token_cost)requestrequest_cost)r  cached_promptinput_cache_readcache_read_cost_per_token)cache_writecache_creationinput_cache_writecache_write_cost_per_token)r  r  r  r  r  c                 X    i | ]'\  }}t          |                                          |(S r   r   rp   r   s      r   r   z$_extract_pricing.<locals>.<dictcomp>z  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   )	r^  r   floatr   r   r   rk  r   rj  )rw  novita_inputnovita_outputr  r  rf  	alias_mapr~  targetr  r  r  r   s              @@r   _extract_pricingr  U  s   ;;899LKK :;;M=#<"$# #E,$7$7&$@9$L M MGH$$'m(<(<v(E	(Q$R$RGL! +5W[[5L5Ld*S*S]w{{:&&&Y]H3;EY///#T** 
s 3 3 3 3(^3 3 3 0 0 
 "$  00<"5):>)J#K#Ki#WXXF8  11=#&u->-O'P'PS\'\#]#]F<   !455A#&u->?R-S'T'TW`'`#a#aF<  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   rl   )rn   
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).N   r0  )rQ  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: rl   )rw   r   rx   _MODEL_CACHE_TTLr   r   requestsr^  r   r   raise_for_statusr   r  r   r   r   r  r   warningmin)r  disk_age
disk_cacheresponser   r   rh   r  r  	canonicalr   s              r   fetch_model_metadatar    s     %2 %	F`8`dt7t7t$$ -9;;H/?$?$?8::J -(2%-1Y[[8-C*,,) < 5wOgOiOijjj!!###}}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  rO  r   r   r   r&  rU  rS  r  rR  rQ  r  rV  r   r  r   loaded_instancesconfigr   r   r  z/modelsr   c              3   r   K   | ]2}t          |t                    |                    d           dk    V  3dS )owned_byrZ  N)r   r   r^  )r   ms     r   r   z0fetch_endpoint_model_metadata.<locals>.<genexpr>8  sV        Jq$4G4Gj!!Z/     r   r   rW  r  rX  rY  r   model_aliasz1Failed to fetch model metadata from %s/models: %s)r   r   r{   r^  r|   r   _ENDPOINT_MODEL_CACHE_TTLr  r   appendrH  rh  r
  r  r   r  r   r   r   rB  r  r  r  r   r   r  r   rt  okr   r   )#r   r   r  r   r$  	cached_at
candidates	alternaterR  
last_errorrd  r  rw  r   rh   r  r  r   instcfgctxr   r  alt_idexc	candidaterC  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'RjRlRlmmmH%%'''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&  rl   r   N)	r  r^  r  nextiterrj  r   r   rB  )rh   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  r  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          p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_loadr^  r   r   r   )r   r   r   r   s       r   _load_context_cacher  x  s    "$$D;;== 	$))) 	+Q>!$$*D	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+xx)**0b0   >BBB						s@   A< AA< AA< !A"A< <
B-B("B-(B-c                 :    |  d|pd                     d           S )zCanonical ``model@base_url`` key for the persistent context cache.

    Trailing slashes are stripped so ``http://host/v1`` and
    ``http://host/v1/`` share one entry instead of creating duplicates
    that can go stale independently.
    @r   r   )r   )rh   r   s     r   _context_cache_keyr    s*     44x~2--c22444r   c                     t          | |          }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  r  r^  r  parentmkdirr   r  dumpr   r(  r   r   )rh   r   r  r   r   r   r   r   s           r   r"  r"    sl    UH
-
-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 <B"C "B&&C )B&*!C 
C=C88C=c                     t          | |          }t                      }|                    |          }||S |  d| | dfD ]#}||k    r|                    |          }||c S $dS )zBLook up a previously discovered context length for model+provider.Nr  r   )r  r  r^  )rh   r   r   r   hit
legacy_keys         r   get_cached_context_lengthr    s    
UH
-
-C!!E
))C..C

  ,,(,,iii8  
))J''C


4r   c                   
 t          | |          }t                      
t          |           }|pd                    d          }t                              ||fd           t                              d||fd           ||  d| | dh}t          
fd|D                       sdS |D ]}
                    |d           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   r   Nollama_showr  c              3       K   | ]}|v V  	d S r   r   )r   r  r   s     r   r   z4_invalidate_cached_context_length.<locals>.<genexpr>  s'      ..aqEz......r   Tr  r  r   r   r  Fr  z6Failed to invalidate context length cache entry %s: %s)r  r  rv   r   r   popr   r  r  r  r   r  r  r   r   r   )rh   r   r   barestripped
stale_keysr  r   r   r   r   s             @r   r)  r)    s   
UH
-
-C!!E "%((DB&&s++Hh/666tX>EEE %,,(,,iii8J....:.....   		!T"$$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<   .D7 D*D7 *D..D7 1D.2D7 7
E(E##E(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)?)\brl   ro  rp  Nrp   rK  searchrB  group)r  error_lowerpatternspatternrr   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    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*\]rl   )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  rr   tokens_m_ctx_m_parts
_available
_m_ctx_tok_m_chars_ctx
_est_input_m_vllm_inputs                    r   (parse_available_output_tokens_from_errorr4    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   rl   F)r   )r9  r:  s     r   _model_id_matchesr<    sH     |##t
l|223::1=MMt5r   c                 Z   ddl }t          |           }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  rO  r&  rI         @rP  	/api/showr   r   rT  
parametersr   num_ctx
r&  r   
model_infor   )r]  rv   rM  r   r  rh  r   r   ra  postrb  r   r^  rn   ro   r  rB  r  r   r   r  )rh   r   r   r]  r  rd  server_typerR  rg  respr   paramslinerE  rD  r   r   s                    r   query_ollama_num_ctxrJ    s    LLL'..J#HOOC$8$899J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( (
A65A6H ()HH BH!F5H7H 
FHFA#H4H HH HH HH 
H('H(c                 ~   ddl }t          |           }|r|sdS 	 t          ||          dk    rdS n# t          $ r Y dS w xY wt	          |                    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&  rI   r   r  rO  r>  rP  r?  r   r@  rT  capabilitiesc              3   `   K   | ])}t          |                                          d k    V  *dS )visionNr  )r   caps     r   r   z/query_ollama_supports_vision.<locals>.<genexpr>8  s7      <<s3xx~~8+<<<<<<r   TFrD  zvision.block_count)r]  rv   rh  r   rM  r   r  r   ra  rE  rb  r   r^  r   rl  r   r   r   rp   )rh   r   r   r]  r  rd  rR  rg  rG  r   capsrD  r   s                r   query_ollama_supports_visionrQ    sv    LLL'..J X t#Hg>>>(JJ4 K   tt $HOOC$8$899J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 ,)DD "D6D DD 	D
D 
DDc                 0   ddl }dt          |           |                    d          f}|                                }t                              |          }|||d         z
  t          k     r|d         S t          | ||          }|r||ft          |<   |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.

    Results are cached in ``_LOCAL_CTX_PROBE_CACHE`` (same 30s TTL,
    positive-only — see ``_query_local_context_length``) so back-to-back
    resolutions during one startup issue a single POST instead of one per
    call site. Failures are never memoized: a server that isn't up yet must
    be re-probed once it comes up.

    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   rl   r&  )r   rv   r   r_  r   r^  _LOCAL_CTX_PROBE_TTL_SECONDS_query_ollama_api_show_uncachedrh   r   r   _time	cache_keynowr$  rf  s           r   _query_ollama_api_showrY  F  s    2 
  6u = =xs?S?STI
//

C#''	22FsVAY2NNNay,UHgNNNF :-3SMy)Mr   c                    ddl }t          |                    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 )uJ   Uncached body of ``_query_ollama_api_show`` — one POST to ``/api/show``.r   Nr   r  rO  g      @rP  r?  r   r@  rT  rD  r   ro  rA  r   rB  rC  r&  r   )r]  rM  r   r  r   ra  rE  rb  r   r^  r   r   rB  r  rn   ro   r  r  r   )rh   r   r   r]  rd  rR  rg  rG  r   rD  r   r   r  rH  rI  rE  s                   r   rT  rT  p  s   LLL#HOOC$8$899J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   G0 -)G$G0 #A;G$G0 +A/G$G7G$9G0 G$
GG$GG$G0 $G((G0 +G(,G0 0
G=<G=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+.
    r:   rT   )rp   rm   )rh   rp   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{  rh   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_3ra    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   rl   r&  )r   rv   r   r_  r   r^  rS  $_query_local_context_length_uncachedrU  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          |           } t          |                    d                    }|                    d          r
|dd         }t          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  rO  r&  r>  rP  rI   r?  r   r@  rT  rA  r   rB  rC  r&  r   rD  r   rU  rS  rV  r   r  r  r  z/v1/models/r   r   z
/v1/modelsr   )r]  rv   rM  r   r  r
  r   rh  r   ra  rE  rb  r   r^  rn   ro   r  rB  r  r   r   r  r<  )rh   r   r   r]  rd  re  rR  rF  rg  rG  r   rH  rI  rE  rD  r   r   r  r  r  r  models_lists                         r   rc  rc    sA   LLL #5))E $HOOC$8$899J5!! %_
%&;H&E&EFFL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   B B"!B"&S, =B2S 0FS S, 
F S F  A#S S, DS "S, /B(S S, $C"S S, S S,  S$$S, 'S$(S, ,
S98S9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.  -)rt  r^  s    r   _normalize_model_versionrh  A  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  rO  z/v1/models?limit=1000z
2023-06-01)z	x-api-keyzanthropic-versionr  r  rT  r   r  r   r   z%Anthropic /v1/models query failed: %s)rm   r   r  r  r^  r   rb  r   r   rB  r   r   r   )rh   r   r   r  rC  rR  rG  r   r  r  r   s              r   _query_anthropic_context_lengthrj  K  sz     g((66 tAs##== 	9D,,, !-
 
 |C'JbJdJd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   r   r   r   zgpt-5.2r   _CODEX_OAUTH_CONTEXT_FALLBACK_codex_oauth_context_cacheaccess_tokenc                     t          j        |                     d                                                    dd         S )z=Return a non-secret cache key for a Codex OAuth access token.r   Nr2  )hashlibsha256encode	hexdigestrm  s    r   _codex_oauth_token_fingerprintrt    s5    >,--g6677AACCCRCHHr   c                    	 |                      d          }t          |          dk     rdS |d         dt          |d                    dz  z  z   }t          j        t	          j        |                    }t          |t                    sdS |                    di                               d          }t          |t                    r|r|ndS # t          $ r Y dS w xY w)	u  Extract ``chatgpt_account_id`` from the Codex OAuth JWT.

    The Codex ``/backend-api/codex/models`` endpoint returns the per-account
    catalog only when the ``ChatGPT-Account-Id`` header is present; without
    it, the endpoint returns ``{"models":[]}`` (HTTP 200) and the context
    probe falls back to the hardcoded defaults — which can be stale or
    wrong for the active account's plan. Mirrors the same extraction done
    in ``auxiliary_client.py`` for the request path.

    Returns ``None`` on any parse error rather than raising, so a bad
    token still surfaces as a normal probe failure instead of crashing
    the metadata resolver.
    r.  r&  Nrl   =r/  zhttps://api.openai.com/authchatgpt_account_id)rn   r  r   loadsbase64urlsafe_b64decoder   r   r^  r   r   )rm  rE  payload_b64claimsacct_ids        r   _extract_chatgpt_account_idr~    s    ""3''u::>>4AhU1X(:!;;F4[AABB&$'' 	4**:B??CCDXYY$Wc22HwHwwDH   tts   (C A C AC 
C C c                    t          j                     }t          |           }t                              |          }||\  }}||z
  t          k     r|dfS dd|  i}t          |           }|r||d<   	 t          j        d|dt                                }|j        d	k    r$t          
                    d
|j                   i dfS |                                }	n6# t          $ r)}
t          
                    d|
           i df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||ft          |<   |dfS )a  Fetch Codex catalogue data and report whether it came from HTTP.

    The in-process cache is scoped by token fingerprint because Codex model
    availability and context windows can vary by account entitlement. The raw
    token is never retained in the cache key. The boolean is false for a
    same-token in-process hit, which must not be treated as a fresh provider
    confirmation when deciding whether to update persistent state.
    NFr   r   zChatGPT-Account-IdzAhttps://chatgpt.com/backend-api/codex/models?client_version=1.0.0r  r  rT  zHCodex /models probe returned HTTP %s; falling back to hardcoded defaultszCodex /models probe failed: %srV  slugr   r   T)r   rt  rl  r^  _CODEX_OAUTH_CONTEXT_CACHE_TTLr~  r  r   rb  r   r   r   r   r   r   r   rB  ro   )rm  rX  rW  r$  cached_modelsr  rR  r}  rG  r   r  entriesrf  rn  r  r  s                   r   ._fetch_codex_oauth_context_lengths_with_sourcer    s%    )++C.|<<I'++I66F#) y?;;; %'' 8, 8 89G),77G 0(/$%|O+--	
 
 
 s""LLZ    u9yy{{   5s;;;5y )34(>(>Fdhhx$$$BGF ' '$%% 	xxhh'((dC   	'ZS%9%9 	'cAgg#&F4::<<  >17"9-4<s%   4AC C 
D&D
D
Dc                 *    t          |           \  }}|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.
    )r  )rm  rf  _freshs      r   "_fetch_codex_oauth_context_lengthsr    s     DLQQNFFMr   c                    t          |                                           }|sdS |rrt          |          \  }}|rdnd}||v r
||         |fS |                                }|                                D ]#\  }}|                                |k    r||fc S $|                                }t          t                                          d d          D ]\  }}||v r|dfc S dS )a  Resolve 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``.

    Returns ``(context_length, source)`` where source is ``"live"`` for a
    value returned by a fresh authenticated endpoint probe, ``"memory"`` for
    a same-token in-process catalogue hit, or ``"fallback"`` for the static
    conservative table. Only ``"live"`` is eligible for persistent writes.
    Nr   livememoryc                 ,    t          | d                   S Nr   r  xs    r   <lambda>zA_resolve_codex_oauth_context_length_with_source.<locals>.<lambda>  s    S1YY r   Tr   reversefallback)rv   ro   r  rp   r   sortedrk  )	rh   rm  
model_barer  fresh_probelive_sourcemodel_lowerr  r  s	            r   /_resolve_codex_oauth_context_length_with_sourcer    s8    (..4466J x 	(J<XXk +9ff
#[00 &&(( 	( 	(ID#zz||{**K'''' + ""$$K%++--3F3FPT   # #	c ;
?"""  8r   c                 .    t          | |          \  }}|S )zEResolve a Codex OAuth model's context length (compatibility wrapper).rs  )r  )rh   rm  r   _sources       r   #_resolve_codex_oauth_context_lengthr    s+     NL  NG r   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   r   z{Rejecting OpenRouter metadata context=%s for %r (Kimi-family underreport, Nous path); falling through to hardcoded defaults)r^  r\  r   r(  )r  r  r  s      r   	_safe_ctxz/_resolve_nous_context_length.<locals>._safe_ctxF  sa    ii());4%<<5e<<<KK^U  
 4
r   r\   r   rl   z-:.r  )r  r  r   r   r   rB  rh  rp   r   rn   rm   r  )rh   r   r   
portal_ctxr  r  r  r   r  r  r  r  r  r  s                 r   _resolve_nous_context_lengthr  %  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    r}	 ddlm}m} ddlm} ddl	m
}	  |            }
|}| ||
          } ||
                    d          pi |           }|                    d          pi }t          |                    d	          pd                                          }t          |                    d
          pd                                          }|r||rz|                                dk    rb |	||          }t          ||                    dd          pd|                    dd          pd|                    d	          p||          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|r7	 t%          t'          |                    }|j        }n# t*          $ r d}Y nw xY wt-          |           } t/          | |          }||S |dk    p3|o1t1          |                              d          ot5          |d          }|rt7          ||          st9          | |          }||dk    r@t;          |           r1t                              d| ||d           t?          | |           n?|dk    r?tA          |           r0t                              d| ||d           t?          | |           n|dk    r?tC          |           r0t                              d| ||d           t?          | |           ntE          |          dk    rt                              d| |           n|r_	 dd l#m$}  ||           }||k     r4t                              d!| ||d|d           t?          | |           |S n# tJ          $ r Y nw xY w|S tM          |          rtO          | |||"          S |S |r	 dd#l#m$}m(} |pd$}t9          | |          }||S d}|r,tS          j*        d%|          }|r|+                    d&          }|s	  |            }n# t          $ r d}Y nw xY w || |tY          |          '          }|r|rt[          | ||           |S # tJ          $ r Y nw xY w|d(k    s|r;t5          |d)          r+t]          | |pd*|"          }||rt[          | ||           |S t_          |          rOta          |          s?t]          | ||"          }||S ta          |          stc          | ||"          }|#t7          ||          st[          | ||           |S tM          |          r=te          | ||"          } | r)| dk    r#t7          ||          stg          | ||            | S t                              d+| |th          d           |                                 }!tk          tl          7                                d, d-          D ],\  }"}#|"|!v r#t                              d.|#d| |"           |#c S -th          S |d/k    s|r*t1          |          d0k    rtq          | |pd1|          }|r|S |}$|$r|$d2v r|rtE          |          }%|%r|%}$|$d3v r)	 dd4l9m:}&  |&| |"          }|r|S n# t          $ r Y nw xY w|$dk    r6tw          | |pd|pd5          \  }}'|r|r|'d6k    rt[          | ||           |S |$d7k    r3ty          | |pd8          \  }(})|(r|r|)d9k    rt[          | ||(           |(S |$d:k    r|rt]          | ||"          }||S |rPtE          |          }*|*duod;|*v}+|+s7tc          | ||"          }|#t7          ||          st[          | ||           |S |$d<k    rlt{                      },|,                    |           }-|-rG|-                    d=          }.t          |.t                    r|.dk    r|.dk    rt;          |           s|.S |$rhdd>l>m?}/  |/|$|           }|rTtA          |           rCtl                              d?          }0|0r'||0k     r!t                              d@|| |0d           |0}|S |$sgt{                      },| |,v rU|,|                              d=th                    }.|.dk    r,t;          |           rt                              dA|.|            n|.S |rLtM          |          r=te          | ||"          } | r)| dk    r#t7          ||          stg          | ||            | S |                                 }!tk          tl          7                                dB d-          D ]\  }"}#|"|!v r|#c S th          S )Ca  Get the context length for a model.

    Resolution order:
    0. Explicit config override (model.context_length or custom_providers per-model)
    0c. Endpoint-scoped metadata for models validated on one multiplexed endpoint
    1. Persistent cache (previously discovered via probing).  Nous URLs,
       LM Studio, and Codex OAuth bypass the cache here so their provider
       metadata can be reconciled against the authoritative live source.
    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)get_compatible_custom_providersload_config)resolve_moa_preset)resolve_runtime_provider
aggregatorr  rh   )r#  target_modelr   r   )r   r   r  r  z/MoA aggregator context-length resolution failedT)exc_info)"get_custom_provider_context_length)rh   r   r  bedrockzbedrock-runtime.zamazonaws.comr   ziDropping stale Kimi cache entry %s@%s -> %s (OpenRouter underreport); re-resolving via hardcoded defaultsr   r   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))get_bedrock_context_lengthzSDropping stale Bedrock cache entry %s@%s -> %s; using static Bedrock table value %sr&  )r  resolve_bedrock_regionz
bedrock://zbedrock-runtime\.([a-z0-9-]+)\.rl   )regionproberG   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 r  r  r  s    r   r  z*get_model_context_length.<locals>.<lambda>	  s    c!A$ii r   r  zUUsing hardcoded context length %s for model %r (custom endpoint, catalog match on %r)rY   r   zhttps://api.anthropic.com>   rC   r\   >   rM   r_   rd   )get_copilot_model_context)r   r   r  rc   rs  r  r3   rI   r\   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 r  r  r  s    r   r  z*get_model_context_length.<locals>.<lambda>O
  s    s1Q4yy r   )@r   rB  ro   rp   hermes_cli.configr  r  hermes_cli.moa_configr  hermes_cli.runtime_providerr  r^  r   get_model_context_lengthr   r   r   r  r	   r   r  r  rv   r  r   rm   r   r  r  r\  r(  r)  r_  ra  r  agent.bedrock_adapterr  ImportErrorrH  r+  r  rK  r  r  r   r"  r  r   r  rY  r'  r#  DEFAULT_FALLBACK_CONTEXTr  DEFAULT_CONTEXT_LENGTHSr   rj  hermes_cli.modelsr  r  r  r  agent.models_devr  )1rh   r   r   r  r  r  r  r  r  r  r  effective_custom_providerspresetaggagg_provider	agg_modelrtr  cp_ctxparsed_base_url_endpoint_contextis_bedrock_contextr$  r  bedrock_ctxr  cache_key_urlr  _mr  r   	local_ctxr  default_modelr  effective_providerinferredr  source	codex_ctxcodex_source_inferred_for_probe_skip_ollama_prober  r  or_ctxr  catalogs1                                                    r   r  r  o  s   D (Z8Ms-S-S(XmpqXqXq$$ 	B%%''500	[        A@@@@@LLLLLL []]F)9&)1-L-LV-T-T*''

5(9(9(?RGGF**\**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VVJ//?<%?     	[ 	[ 	[LLJUYLZZZZZ	[  H  
	LLLLLL77!!1  F
   	 	 	D	  	&':8'D'DEEO$AA 	 	 	HHH	 #5))E
 7uhGG#!Y.  	=h''223EFF	=!(O<<   P6xJJ P*5(;;#<U#C#C: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    $ PPPPPP"<"<U"C"CK++B!$%MM*..   :%JJJ** , #   D$X.. Ax      %$	        %4M.umDDF! F )YA8LL )XXa[[F   3355FF       FFF ,,U6fVVVC ?v ? $E=#>>>J?  	 	 	D	B 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^++ #R2#
 #
 #
	<  	  @LF22#E8Y???U""x" /uhPPP?J  6x@@t+ 4 33 	 " 	('JJJC5hII >'x===
 \))'))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 ( $#s   EF &GGG* *
G76G7=#H! !H0/H0:A
P 
PP?S! 
R R,+R,!
S.-S.'[? ?
\\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   r  r  r  )asyncio	to_threadr  )rh   r   r   r  r  r  r  s          r   get_model_context_length_asyncr  X
  sa      " NNN"" 3) #         r   chc                     t          |           }d|cxk    odk    nc pOd|cxk    odk    nc p?d|cxk    odk    nc p/d|cxk    odk    nc pd	|cxk    od
k    nc pd|cxk    odk    nc S )Ni   i  i.  i  i`  i  i   i  i   i  i   i  )ord)r  codes     r   _is_cjk_token_dense_charr  u
  s    r77D$    &     	$T####V####	$T####V####	$ T####V####	$ T####V####		$
 T####V####r   u,   [ᄀ-ᇿ⺀-鿿ꥠ-꥿가-힯豈-﫿＀-￯]rc  c                 R   | sdS t          |           } |                                 rt          |           dz   dz  S t          |           t          t                              d|                     z
  }|st          |           dz   dz  S t          |           |z
  }||dz   dz  z   S )a=  Rough token estimate 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.
    CJK/Hangul/Kana text is much denser than English under common LLM
    tokenizers, so count those codepoints as roughly one token each instead
    of applying the English-centric ~4 chars/token rule.

    Perf: this runs on every message in every preflight/compaction walk,
    including MB-scale tool outputs, so the common all-ASCII case must stay
    O(1).  ``str.isascii()`` is a flag check on CPython's compact unicode
    representation (no scan), and the CJK counting itself is a single
    C-level ``re.findall`` rather than a per-character Python loop.
    r   r'  r/  r   )r   isasciir  _CJK_DENSE_RErL  )rc  densesparses      r   estimate_tokens_roughr  
  s       qt99D||~~ $D		A!##IIM--b$77888E $ D		A!##YYFVaZA%&&r   messagesc                 l    d}d}d}| D ]'}|t          |          z  }|t          ||          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   )'_estimate_message_tokens_without_images_count_image_tokens)r  _IMAGE_TOKEN_COSTtext_tokensimage_tokensmsgs        r   estimate_messages_tokens_roughr  
  s[     KL D D>sCCC+C1BCCC%%r   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_imagerl   _anthropic_content_blocksr  _multimodal>   r  r  )r   r   r^  rl  )r  r  rJ  r  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  
[stripped]r  r  r  text_summaryr   )r   r   r  r   r   rl  r^  r  r  shadowr  vcleanedr  s         r   _estimate_message_charsr
  
  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   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 )
zAToken estimate for a message shadow with image payloads stripped.r  r  r  >   r  r  r  r  r  r  r  r   )r   r   r  r   r   rl  r^  r  r  s         r   r  r  
  sv   c4   /$SXX...F		  1+++	>>!T""  - -D!$-- -88F++/TTT#NNDHHV4D4D|+\+\]]]]#NN40000t,,,,#q		At$$ })=)= EE."55q		q		F1II V---r   )system_prompttoolsr  r  c                    d}|r|t          |          z  }| r|t          |           z  }|r|t          |          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  _estimate_tools_tokens_rough)r  r  r  totals       r   estimate_request_tokens_roughr    s_     E 6&}555 :/999 5-e444Lr   _TOOLS_TOKENS_CACHE   toolc                 8   t          | t                    sdS |                     d          }t          |t                    r,|                    d          }t          |t                    r|S |                     d          }t          |t                    r|ndS )Nr   functionr   )r   r   r^  r   )r  fnr   s      r   _tool_name_for_cacher  :  s    dD!! r	*		B"d vvf~~dC   	K88FDdC((044b0r   c           	         | sdS t          |           }t          |           }|rt          | d                   nd}|rt          | d                   nd}t                              |          }||\  }}}}	||k    r||k    r||k    r|	S d}
| D ]t}t          |t                    s|                    d          }t          |t                    rF|                    d          pd}|                    d          pd}|                    d          pi }nE|                    d          pd}|                    d          pd}|                    d          pi }t          |t                    r|
t          |          z  }
t          |t                    r|
t          |          z  }
	 |
t          t          j	        |dd	
                    z  }
F# t          $ r# |
t          t          |                    z  }
Y rw xY w|
dz   dz  }t          t                    t          k    r:t                              t          t          t                              d            ||||ft          |<   |S )Nr   r   r   r  r   descriptionrA  Fr   )ensure_asciir   r'  r/  )r  r  r  r  r^  r   r   r   r   dumpsr   _TOOLS_TOKENS_CACHE_MAXr  r  r  )r  r   nrF  lastr$  cached_ncached_firstcached_lastcached_tokenstotal_charsr  r  r   descrH  r+  s                    r   r  r  F  s    q U))CE

A./7 q***RE./7b	***RD $$S))F=C:,]q==\U22{d7J7J  
 K , ,$%% 	XXj!!b$ 	266&>>'RD66-((.BDVVL))/RFF88F##)rD88M**0bDXXl++1rFdC   	%3t99$KdC   	%3t99$K	,3tz&uQ[\\\]]]KK 	, 	, 	,3s6{{+++KKK	, Ao!#F #:::T*=%>%> ? ?FFF !5$7Ms   -'G)HH)r   )ro  rp  )F)r   F)r   r   )r   r   Nr   N)__doc__ry  ro  r;  r   loggingr   rK  r   pathlibr   typingr   r   r   r   r   urllib.parser	   r  r  utilsr
   r   r   r   r   	getLogger__name__r   r   r   r   	frozensetrg   __annotations__compile
IGNORECASErq   IPv4NetworkrA  rv   rw   rx   r  ry   rz   r  r{   r|   r  r`  r}   tupler   r   r   r   r  r  r!  rS  r   r  r   r   r  r  r9  r:  r   r   r   r   r   	providersr   _list_providers_ppget_hostname_hostr   r   r  r
  r  rB  r  r  r#  r+  rH  rM  rh  rk  rv  r  r  r  r  r  r  r  r  r  r  r  r"  r  r)  r  r  r  r4  r(  r<  rJ  rQ  rY  rT  r\  r_  ra  r'  rc  rh  rj  rk  rl  r  rt  r~  r  r  r  r  r  rl  r  r  r  r  r  r  r  r
  r  r  r  r   r  r  r  r   r   r   <module>r9     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 : : :  % /1 De, 1 1 1J J J J J    c4S>.A)B    $
S$sDcN/B*C 
S 
S 
S 
S 
S     /q1 
     $ -/ UE\* / / /]
 g] G] w] w] w] w] w] w] ] w] ]" f#]6 G7]8 W9]: 7;]< w=]> F?] ]@ FA]B wC]P 6Q]R FS]T VU]V wW]X VY]\ g]]` va]b fc]d 6e]f vg]h Ti]x yy]z {]| Y}]~ ] ] ]@ A]D VE]J GK]L GM]N O]P 6Q]R S]T FU]` 'a]b vc]p yq]r 
6s]H VI]J K]L &M]N fO]P TQ] ] ]R 7S]T U]V W]X Y]Z f[]\ f]]^ f_]` Fa]l ym]n Fo]| 6}]~ &]@ %A]B %C]J 6K]N 
6O]R S] ] ]V vW]Z []^ f_]` Fa]b  c]d Fe]f Fg]h "6i]j fk]l m]n 7o]p Wq]r s]t Fu]v Vw]x Vy] ] X	! 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    :# :$ : : : :3 # (3-    :LS LC LD L L L L555 5 
	5 5 5 5( 	" """ " 	"
 	" " " "J5 5 5 5 5 5pC C    .O Os OS O(3- O O O Od0c 0 0 0 0 #  S ZbcfZg    	S#X 	eCHo 	(SV- 	 	 	 	=T#s(^ = = = = ==DcN =x} = = = =0d38n 0c3h 0 0 0 0f,d3S#X#67 ,3 ,tTWY\T\~ ,bf , , , ,9 9 9c4S>>Q9R 9 9 9 9| M MMM M 
#tCH~
	M M M Mf    c]	   .; ; ; ; ;T#s(^    5c 5S 5S 5 5 5 5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8     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 ' ' ' 'T) )3 )# ) )U]^aUb ) ) ) )X	;S 	;T 	; 	; 	; 	;	)3 	)4 	) 	) 	) 	)	' 	' 	' 	' 	' 	' s c C QYZ]Q^    D\ \ \s \S \ZbcfZg \ \ \ \~#C #C # # # #3 #  PXY\P]    P !! #%1 1 tCH~   , GI DeDcNE,A&B!BC H H H!% I I I I I I
c hsm    877
4S>4 7 7 7 7t
S 
T#s(^ 
 
 
 
 %'$ $$!$
8C=#$ $ $ $P %' !c]    G GGG G 8C=#	G G G GX (,$(f$ f$f$f$ f$ :	f$
 f$ Tkf$ 	f$ f$ f$ f$V (,$(   :	
  Tk 	   :	 	 	 	 	 	  
 ' ' ' ' ' '>&T$sCx.-A &c & & & &""T#s(^ "S "S " " " "4c3h C    B.c3h .C . . . .@ ,0	  4S>"  Dc3h()	
 	   > =? T#uS#sC%7889 > > > 	1s 	1s 	1 	1 	1 	13T#s(^(< 3 3 3 3 3 3 3s   
7N N
	N
