
    Jj                      U d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddlZddlZddlZddlZddlZddlmZ ddlmZmZ ddlmZmZ ddlmZmZmZ ddlmZ dd	lm Z m!Z!m"Z"m#Z#m$Z$m%Z%m&Z&m'Z' dd
l(m)Z)m*Z*m+Z+ ddl,Z,ddl-m.Z.m/Z/m0Z0m1Z1 ddl2m3Z3m4Z4 ddl5m6Z6 ddl7m8Z8m9Z9m:Z:m;Z;  ej<        e=          Z>	 ddl?Z?n# e@$ r dZ?Y nw xY w	 ddlAZAn# e@$ r dZAY nw xY wdZBdZCdZDdZEdZFdZGdZHeGZIdZJdZKdZLeLZMdZNdZOdZPdZQdZRdZSdZTdZUd ZVd!ZWd"ZXd#ZYd$ZZd%Z[d&Z\d'Z]d(Z^d)Z_d*Z`	 dd+lambZc n# e@$ r d,ZcY nw xY wd-ec ZddZed.Zfef d/Zgd0Zhd1Zief d2Zjd3Zkd4Zld5ZmdZnd6Zod7Zpd8Zqd9Zrd:ZsdZtd;Zud<v                    d=          Zwd>d?iZxd@eydA<   dBZze G dC dD                      Z{i dE e{dEdFdGeDeEeFeIH          dI e{dIdJdKeOL          dM e{dMdNdOdPdQdRS          dT e{dTdUdKePL          dV e{dVdWdKeYL          dX e{dXdYdOdZd[d\S          d] e{d]d^dOeZd_d`S          da e{dadbdce[dde          df e{dfdgdOdhdidjS          dk e{dkdldOdmdndoS          dp e{dpdqdOdrdsdtS          du e{dudvdOdwdxy          dz e{dzd{dOe]d|d}S          d~ e{d~ddOdddS          d e{dddOdddS          d e{dddOd ddS          d e{dddeTeVeQeRdeUeWd          i d e{dddOdddS          d e{dddOdddS          d e{dddOdddS          d e{dddOd!ddS          d e{dddOdddS          d e{dddOdddS          d e{dddOdddS          d e{dddOdddS          d e{dddOdddS          d e{dddOdddS          d e{dddOdddƬS          d e{dddOdddˬS          d e{dddOdddЬS          d e{dddOe\ddԬS          d e{ddddddڬS          d e{dddOddd߬S          Z|deyd<   	 ddl}m~Z  e            D ]Zej        e|v rej        dOk    sej        sej        dv r) ed ej        D                       Z ed ej        D             d          Z e{ej        ej        pej        dOej        epej        epdݬS          e|ej        <   ej        D ]Zee|vre|ej                 e|e<   n# e@$ r Y nw xY wddZdZddZh dZddddZddZddmdgdfdddgdfdd g ddfddg ddfgZddd
ZddZddZdZ G d de          ZddZddZddZddZddZddZdddd"Zdd$Zdd&Zdd(Zdd)Z ej                    Zedd/            ZeeCfdd0            Zddd2Zddd5Zdd7Zdd9Zdd;Zdd=Zd>d?ddAZddBZddCZddDZdddEZddFddKZddMZddNZddOZddPZddQZddRZdddSZddTZddVZ	 ddddWdd[Zdd]Zdd`ZddbZddcZ eddh          ZÐdeeydf<    eh dg          ZĐdeeydh<   ddjZ edkh          ZƐdeeydl<   ddnZǐddoZȐddpZɐddqZʐddtZddeMduddyZddeMduddzZdd{dd}Zdddd~ZϐdddZddddZdddddZ edadh          ZӐddZԐddZՐddZ֐ddZאddZenfddZِdddZڐddZېdd>endddZܐddZݐddÐdZސddĐdZ	 	 dŐdƐdZ	 	 dŐdƐdZddǐdZddǐdZdȐdɐdZdʐdZdȐdɐdZdʐdZdːdZd̐dZd͐dZdddΐdZdddϐdZdddАdZdddѐdZdd>etdddZddZdҐdÄZddĄZddńZ eh dƣ          Zdeeyd<   ddȄZddɄZddʜdӐd̈́Zd>dΜdԐdЄZ	 ddՐdӄZdŐd֐dՄZdאdׄZdddؐdلZdِdڄZdڐdۄZdd>eeddd܄Zdېd݄Z ddބZdܐd߄ZdݐdZd>dΜdԐdZdސdZdߐdZddddGdddZdddZddZ	ddZ
ddZdddZdݐddddZdݐdddZdd>ddddZddZddddddZdd ZddZdZ ej                    ZddZddZeeCfdd            ZddZdߐdZdېd	Zdd
ZddZddZddZddZ ddZ!ddddZ"ddZ#dd>dddZ$ddZ%dddeLdddZ&deIdddddddddddd"Z'dddd#dd$Z(dd%dd&Z)dd'Z*ddddd(dd)Z+dd*Z,dd+Z-dZ.da/d,eyd-<   dd/Z0dd0Z1dd1Z2dd2Z3dd3Z4dd4Z5dd5Z6dd6Z7ddd7Z8dd8Z9dd9Z:dd:Z;	 ddd<Z<dd=Z=dd>Z>dd?Z?dd@Z@ddAZAdddݐdBd dEZB	 	 	 	 	 	 	 	 dddQZCddRZDddSZEddTddVZFddTddWZGeidXddYZHddZZIdd>d[dd]ZJdd^ZKdd`ZLd	daZMd
ddZNddgZOddjZPddkZQdd>ddlddnZRdddoddqZSddrZTddtZUeXdduddxZVddyZWddzZXddddd>ddddd{	dd~ZYddZZd>dddddZ[ddZ\ddZ]dS (  a  
Multi-provider authentication system for Hermes Agent.

Supports OAuth device code flows (Nous Portal, future: OpenAI Codex) and
traditional API key providers (OpenRouter, custom endpoints). Auth state
is persisted in ~/.hermes/auth.json with cross-process file locking.

Architecture:
- ProviderConfig registry defines known OAuth providers
- Auth store (auth.json) holds per-provider credential state
- resolve_provider() picks the active provider via priority chain
- resolve_*_runtime_credentials() handles token refresh and runtime keys
- logout_command() is the CLI entry point for clearing auth

Nous authentication paths:
- Invoke JWT (preferred): use a scoped access_token directly for inference.
    )annotationsN)contextmanager)	dataclassfield)datetimetimezone)BaseHTTPRequestHandler
HTTPServerThreadingHTTPServer)Path)AnyCallableDict	FrozenSetIterableListOptionalTuple)parse_qs	urlencodeurlparse)get_hermes_homeget_config_pathread_raw_config$require_readable_config_before_write)OPENROUTER_BASE_URLsecure_parent_dir)$sanitize_borrowed_credential_payload)atomic_replaceatomic_yaml_write	env_floatis_truthy_value         .@zhttps://portal.nousresearch.comz)https://inference-api.nousresearch.com/v1
hermes-clizinference:invokezbilling:managedevice_code
invoke_jwtx   z%https://chatgpt.com/backend-api/codexzhttps://api.x.ai/v1z$78257093-7e40-4613-99e0-527b14b39113z!group_id profile model.completionz*urn:ietf:params:oauth:grant-type:user_codezhttps://api.minimax.iozhttps://api.minimaxi.comz https://api.minimax.io/anthropicz"https://api.minimaxi.com/anthropic<   zhttps://portal.qwen.ai/v1zhttps://api.githubcopilot.comzacp://copilotzhttps://ollama.com/v1z#https://api.stepfun.ai/step_plan/v1z$https://api.stepfun.com/step_plan/v1app_EMoamEEZ73f0CkXaXp7hrannz#https://auth.openai.com/oauth/token)__version__unknownzhermes-cli/zhttps://auth.x.aiz!/.well-known/openid-configurationz$b1a00492-073a-47ea-816f-4c329264a828z>openid profile email offline_access grok-cli:access api:accessz/oauth2/device/codei   f0304373b74a44d2b584a3fb70ca9e56z(https://chat.qwen.ai/api/v1/oauth2/tokenzhttps://accounts.spotify.comzhttps://api.spotify.com/v1z'http://127.0.0.1:43827/spotify/callbackzFhttps://hermes-agent.nousresearch.com/docs/user-guide/features/spotifyz'https://developer.spotify.com/dashboardz@https://hermes-agent.nousresearch.com/docs/guides/oauth-over-ssh )
zuser-modify-playback-statezuser-read-playback-statezuser-read-currently-playingzuser-read-recently-playedzplaylist-read-privatezplaylist-read-collaborativezplaylist-modify-publiczplaylist-modify-privatezuser-library-readzuser-library-modifyspotifySpotifyDict[str, str]SERVICE_PROVIDER_NAMESzdummy-lm-api-keyc                      e Zd ZU dZded<   ded<   ded<   dZded<   dZded<   dZded	<   dZded
<    e	e
          Zded<   dZded<   dZded<   dS )ProviderConfigz%Describes a known inference provider.stridname	auth_type portal_base_urlinference_base_url	client_idscope)default_factoryDict[str, Any]extra tupleapi_key_env_varsbase_url_env_varN)__name__
__module____qualname____doc____annotations__r:   r;   r<   r=   r   dictr@   rC   rD   rA       5/home/rurouni/.hermes/hermes-agent/hermes_cli/auth.pyr4   r4      s         //GGGIIINNNO     IEOOOO!E$777E7777     rK   r4   nouszNous Portaloauth_device_code)r6   r7   r8   r:   r;   r<   r=   openai-codexzOpenAI Codexoauth_external)r6   r7   r8   r;   z
openai-apiz
OpenAI APIapi_keyzhttps://api.openai.com/v1)OPENAI_API_KEYOPENAI_BASE_URL)r6   r7   r8   r;   rC   rD   	xai-oauthz%xAI Grok OAuth (SuperGrok / Premium+)
qwen-oauthz
Qwen OAuthlmstudioz	LM Studiozhttp://127.0.0.1:1234/v1)
LM_API_KEYLM_BASE_URLcopilotzGitHub Copilot)COPILOT_GITHUB_TOKENGH_TOKENGITHUB_TOKENCOPILOT_API_BASE_URLcopilot-acpzGitHub Copilot ACPexternal_processCOPILOT_ACP_BASE_URL)r6   r7   r8   r;   rD   geminizGoogle AI Studioz0https://generativelanguage.googleapis.com/v1beta)GOOGLE_API_KEYGEMINI_API_KEYGEMINI_BASE_URLzaiz
Z.AI / GLMzhttps://api.z.ai/api/paas/v4)GLM_API_KEYZAI_API_KEYZ_AI_API_KEYGLM_BASE_URLkimi-codingzKimi / Moonshotzhttps://api.moonshot.ai/v1)KIMI_API_KEYKIMI_CODING_API_KEYKIMI_BASE_URLkimi-coding-cnzKimi / Moonshot (China)zhttps://api.moonshot.cn/v1)KIMI_CN_API_KEY)r6   r7   r8   r;   rC   stepfunzStepFun Step Plan)STEPFUN_API_KEYSTEPFUN_BASE_URLarceezArcee AIzhttps://api.arcee.ai/api/v1)ARCEEAI_API_KEYARCEE_BASE_URLgmiz	GMI Cloudzhttps://api.gmi-serving.com/v1)GMI_API_KEYGMI_BASE_URLminimaxMiniMax)MINIMAX_API_KEYMINIMAX_BASE_URLminimax-oauthu   MiniMax (OAuth · minimax.io)oauth_minimaxglobal)regioncn_portal_base_urlcn_inference_base_url)r6   r7   r8   r:   r;   r<   r=   r@   	anthropic	Anthropiczhttps://api.anthropic.com)ANTHROPIC_API_KEYANTHROPIC_TOKENCLAUDE_CODE_OAUTH_TOKENANTHROPIC_BASE_URLalibabaz
Qwen Cloudz6https://dashscope-intl.aliyuncs.com/compatible-mode/v1)DASHSCOPE_API_KEYDASHSCOPE_BASE_URLalibaba-coding-planzAlibaba Cloud (Coding Plan)z-https://coding-intl.dashscope.aliyuncs.com/v1)ALIBABA_CODING_PLAN_API_KEYr   ALIBABA_CODING_PLAN_BASE_URL
minimax-cnzMiniMax (China))MINIMAX_CN_API_KEYMINIMAX_CN_BASE_URLdeepseekDeepSeekzhttps://api.deepseek.com/v1)DEEPSEEK_API_KEYDEEPSEEK_BASE_URLxaixAI)XAI_API_KEYXAI_BASE_URLnvidiaz
NVIDIA NIMz#https://integrate.api.nvidia.com/v1)NVIDIA_API_KEYNVIDIA_BASE_URLopencode-zenzOpenCode Zenzhttps://opencode.ai/zen/v1)OPENCODE_ZEN_API_KEYOPENCODE_ZEN_BASE_URLopencode-gozOpenCode Gozhttps://opencode.ai/zen/go/v1)OPENCODE_GO_API_KEYOPENCODE_GO_BASE_URLkilocodez	Kilo Codezhttps://api.kilo.ai/api/gateway)KILOCODE_API_KEYKILOCODE_BASE_URLhuggingfacezHugging Facez https://router.huggingface.co/v1)HF_TOKENHF_BASE_URLxiaomizXiaomi MiMozhttps://api.xiaomimimo.com/v1)XIAOMI_API_KEYXIAOMI_BASE_URLtencent-tokenhubzTencent TokenHubz#https://tokenhub.tencentmaas.com/v1)TOKENHUB_API_KEYTOKENHUB_BASE_URLollama-cloudzOllama Cloud)OLLAMA_API_KEYOLLAMA_BASE_URLbedrockzAWS Bedrockaws_sdkz/https://bedrock-runtime.us-east-1.amazonaws.comrA   BEDROCK_BASE_URLazure-foundryzAzure Foundryr9   )AZURE_FOUNDRY_API_KEYAZURE_FOUNDRY_BASE_URLzDict[str, ProviderConfig]PROVIDER_REGISTRYlist_providers>   re   customrY   
openrouterrj   rn   c              #  n   K   | ]0}|                     d           |                     d          ,|V  1dS 	_BASE_URL_URLNendswith.0vs     rL   	<genexpr>r     sH      ppAQZZ=T=Tp]^]g]ghn]o]opapppppprK   c              #  n   K   | ]0}|                     d           s|                     d          ,|V  1dS r   r   r   s     rL   r   r     sH      ggAK9P9PgTUT^T^_eTfTfgaggggggrK   returnr5   c                 b    ddl m}  t          d         j        D ]} | |          pd}|r|c S dS )u  Return the first usable Anthropic credential, or ``""``.

    Checks both the ``.env`` file and the process environment, preferring
    ``~/.hermes/.env`` so a deliberate key rotation isn't shadowed by a stale
    shell export (matches the api-key resolution path — see #20591).  The
    order mirrors the ``PROVIDER_REGISTRY["anthropic"].api_key_env_vars``
    tuple:

        ANTHROPIC_API_KEY -> ANTHROPIC_TOKEN -> CLAUDE_CODE_OAUTH_TOKEN
    r   get_env_value_prefer_dotenvr   r9   )hermes_cli.configr   r   rC   )r   varvalues      rL   get_anthropic_keyr     s]     >===== ->  ++C006B 	LLL	2rK   zhttps://api.kimi.com/codingdefault_urlenv_overridec                N    |r|S | s|S |                      d          rt          S |S )zReturn the correct Kimi base URL based on the API key prefix.

    If the user has explicitly set KIMI_BASE_URL, that always wins.
    Otherwise, sk-kimi- prefixed keys route to api.kimi.com/coding/v1.
    zsk-kimi-)
startswithKIMI_CODE_BASE_URL)rQ   r   r   s      rL   _resolve_kimi_base_urlr     sB       *%% "!!rK   >   *****your-api-key*nonenulldummyexamplechangemeplaceholderyour_api_keyyour_api_key_here   )
min_lengthr   r   r   intboolc                   t          | t                    sdS |                                 }t          |          |k     rdS |                                t
          v rdS dS )zIReturn True when a configured secret looks usable, not empty/placeholder.FT)
isinstancer5   striplenlower_PLACEHOLDER_SECRET_VALUES)r   r   cleaneds      rL   has_usable_secretr   ,  sZ    eS!! ukkmmG
7||j  u}}444u4rK   provider_idpconfigtuple[str, str]c                   | dk    rj	 ddl m}m}  |            \  }}|r ||          \  }}||fS n=# t          $ r%}t                              d|           Y d}~nd}~wt          $ r Y nw xY wdS ddlm}	 |j	        D ]6}
 |	|
          pd
                                }t          |          r||
fc S 7	 dd	lm}  ||           }|r|                                ro|                                }|rYt!          |d
d          pt!          |dd          }t#          |          
                                }t          |          r|d|  fS n# t          $ r Y nw xY wdS )zDResolve an API-key provider's token and indicate where it came from.rY   r   resolve_copilot_tokenget_copilot_api_tokenz#Copilot token validation failed: %sN)r9   r9   r   r9   	load_poolaccess_tokenruntime_api_keyzcredential_pool:)hermes_cli.copilot_authr   r   
ValueErrorloggerwarning	Exceptionr   r   rC   r   r   agent.credential_poolr   has_credentialspeekgetattrr5   )r   r   r   r   tokensource	api_token	_base_urlexcr   env_varvalr   poolentrykeys                   rL    _resolve_api_key_provider_secretr  8  s    i		\\\\\\\\1133ME6 )'<'<U'C'C$	9 &(()  	G 	G 	GNN@#FFFFFFFF 	 	 	D	v======+     +*7339r@@BBS!! 	 <	 333333y%% 	AD((** 	AIIKKE Ae^R88aGEK\^`<a<a#hhnn&&$S)) A @; @ @@@    6s-   (2 
A,AA,+A,6BE 
EEzglm-5Globalcnz$https://open.bigmodel.cn/api/paas/v4Chinazcoding-globalz#https://api.z.ai/api/coding/paas/v4)zglm-5.2zglm-5.1zglm-5v-turbozglm-4.7zGlobal (Coding Plan)z	coding-cnz+https://open.bigmodel.cn/api/coding/paas/v4zChina (Coding Plan)       @timeoutfloatOptional[Dict[str, str]]c                   t           D ]\  }}}}|D ]}	 t          j        | dd|  dd|ddddd	gd
|          }|j        dk    r(t                              d|||           ||||dc c S t                              d|||j                   # t          $ r'}t                              d|||           Y d}~d}~ww xY wdS )a+  Probe z.ai endpoints to find one that accepts this API key.

    Returns {"id": ..., "base_url": ..., "model": ..., "label": ...} for the
    first working endpoint, or None if all fail.  For endpoints with multiple
    candidate models, tries each in order and returns the first that succeeds.
    z/chat/completionsBearer application/json)AuthorizationContent-TypeFr#   userping)rolecontent)modelstream
max_tokensmessages)headersjsonr     z(Z.AI endpoint probe: %s (%s) model=%s OK)r6   base_urlr  labelz,Z.AI endpoint probe: %s model=%s returned %sz+Z.AI endpoint probe: %s model=%s failed: %sN)ZAI_ENDPOINTShttpxpoststatus_coder   debugr   )	rQ   r  ep_idr$  probe_modelsr%  r  respr  s	            rL   detect_zai_endpointr.  x  sn    1> _ _,xu! 	_ 	_E_z222)<7)<)<(: 
 "'"'&'.4%H%H$I	  $   #s**LL!KUT\^cddd#$,!&!&	       KUTY[_[kllll _ _ _JESXZ]^^^^^^^^_3	_6 4s   AB1"B
CC  Cc                   |r|S | s|S t                      }t          |d          pi }|                    d          }t          |t                    r|                    d          r|                    dd          }|t          j        |                                                                           dd         k    r)t          
                    d|d                    |d         S t          |           }|r|                    d          rt          j        |                                                                           dd         }|d         |                    d	d          |                    d
d          |                    dd          |d|d<   t          |d|           t                              d|d         |d                    |d         S t          
                    d|           |S )aY  Return the correct Z.AI base URL by probing endpoints.

    If the user has explicitly set GLM_BASE_URL, that always wins.
    Otherwise, probe the candidate endpoints to find one that accepts the
    key.  The detected endpoint is cached in provider state (auth.json) keyed
    on a hash of the API key so subsequent starts skip the probe.
    re   detected_endpointr$  key_hashr9   N   zZ.AI: using cached endpoint %sr6   r  r%  )r$  endpoint_idr  r%  r1  z$Z.AI: auto-detected endpoint %s (%s)z.Z.AI: probe failed, falling back to default %s)_load_auth_store_load_provider_stategetr   rJ   hashlibsha256encode	hexdigestr   r*  r.  _save_provider_stateinfo)rQ   r   r   
auth_storestatecachedr1  detecteds           rL   _resolve_zai_base_urlrA    s        "##J U339rEYY*++F&$ &FJJz$:$: &::j"--w~gnn&6&677AACCCRCHHHLL96*;MNNN*%% #7++H $HLL,, $>'.."2"233==??D ,#<<b11\\'2..\\'2.. &
 &
!" 	Z666:HW<MxXbOcddd
##
LLA;OOOrK   r$  c                
   t          | pd                                                              d          }dD ]D}|                    |          r-|dt	          |                                        d          } nE|pddz   S )aP  Return the OpenAI-compatible LM Studio runtime base URL.

    LM Studio's native management API lives under ``/api/v1`` while its
    OpenAI-compatible chat endpoint lives under ``/v1``. Users often paste
    either form into ``LM_BASE_URL`` or ``model.base_url``; normalize before
    the OpenAI SDK appends ``/chat/completions``.
    r9   /)z/api/v1z/api/v1Nzhttp://127.0.0.1:1234rD  )r5   r   rstripr   r   )r$  rootsuffixs      rL   $_normalize_lmstudio_runtime_base_urlrH    s     x~2$$&&--c22D,  ==   	3v;;,'..s33DE	 ++u44rK   codex_rate_limitedc                  .     e Zd ZdZddddd fdZ xZS )	AuthErrorz,Structured auth error with UX mapping hints.r9   NFprovidercoderelogin_requiredmessager5   rM  rN  Optional[str]rO  r   r   Nonec               t    t                                          |           || _        || _        || _        d S N)super__init__rM  rN  rO  )selfrP  rM  rN  rO  	__class__s        rL   rV  zAuthError.__init__  s9     	!!! 	 0rK   )
rP  r5   rM  r5   rN  rQ  rO  r   r   rR  )rE   rF   rG   rH   rV  __classcell__)rX  s   @rL   rK  rK    sX        66 "!&1 1 1 1 1 1 1 1 1 1 1 1rK   rK  errorr   c                \    t          | t                    o| j         o| j        t          k    S )uh  True when an :class:`AuthError` represents upstream rate-limiting / quota
    exhaustion rather than missing or invalid credentials.

    These failures are transient — re-authenticating cannot resolve them — so
    callers should surface a "retry later" notice and prefer a fallback chain
    instead of prompting the operator to run ``hermes auth``.
    )r   rK  rO  rN  CODEX_RATE_LIMITED_CODErZ  s    rL   is_rate_limited_auth_errorr^    s4     	5)$$ 	2&&	2J11rK   r!  Optional[int]c                   | dS 	 |                      d          }n# t          $ r Y dS w xY w|dS 	 t          t          |                                                    }n# t
          t          f$ r Y dS w xY w|dk    r|ndS )zBest-effort parse of a ``Retry-After`` header into whole seconds.

    Supports the delta-seconds form (e.g. ``"120"``). HTTP-date forms and
    missing/unparseable values return ``None`` rather than guessing.
    Nzretry-afterr   )r6  r   r   r5   r   	TypeErrorr   )r!  rawsecondss      rL   _parse_retry_after_secondsrd  	  s     tkk-((   tt
{tc#hhnn&&''z"   ttll77,s    
**.A! !A65A6c                   t          | t                    st          |           S t          |           rt          |           S | j        r|  dS | j        dk    r| j        dk    rt          |           S dS | j        dk    r| j        dk    rt          |           S dS | j        dv r| j        dk    rt          |           S | j        dk    r|  d	S t          |           S )
z2Map auth failures to concise user-facing guidance.z' Run `hermes model` to re-authenticate.subscription_requiredrM   zWNo active paid subscription found. Please purchase/activate a subscription, then retry.insufficient_creditszESubscription credits are exhausted. Top up/renew credits, then retry.>   account_missingno_usable_creditssubscription_expiredtemporarily_unavailablez Please retry in a few seconds.)r   rK  r5   r^  rO  rN  rM  #_format_nous_entitlement_auth_errorr]  s    rL   format_auth_errorrm    s	   eY'' 5zz "%(( 5zz A@@@@z,,,>V##6u===hhz+++>V##6u===VVzUUU>V##6u===z...8888u::rK   c                z    	 ddl m}m}  |d          } ||d          }|r|S n# t          $ r Y nw xY w|  dS )Nr   &format_nous_portal_entitlement_messageget_nous_portal_account_infoTforce_freshzNous model access
capabilityz5 Check credits or billing in Nous Portal, then retry.)hermes_cli.nous_accountrp  rq  r   )rZ  rp  rq  account_inforP  s        rL   rl  rl  ?  s    	
 	
 	
 	
 	
 	
 	
 	

 43EEE88*
 
 
  	N	   JJJJs   $( 
55r  rQ  c                    t          | t                    sdS |                                 }|sdS t          j        |                    d                                                    dd         S )zJReturn a short hash fingerprint for telemetry without leaking token bytes.Nutf-8   )r   r5   r   r7  r8  r9  r:  )r  r   s     rL   _token_fingerprintr{  R  sd    eS!! tkkmmG t>'..1122<<>>ssCCrK   c                 |    t          j        dd                                                                          } | dv S )NHERMES_OAUTH_TRACEr9   >   1onyestrue)osgetenvr   r   )rb  s    rL   _oauth_trace_enabledr  \  s8    
)("
-
-
3
3
5
5
;
;
=
=C,,,rK   )sequence_ideventr  fieldsrR  c                   t                      sd S d| i}|r||d<   |                    |           t                              dt	          j        |dd                     d S )Nr  r  zoauth_trace %sTF)	sort_keysensure_ascii)r  updater   r<  r"  dumps)r  r  r  payloads       rL   _oauth_tracer  a  sp    !! &.G -!,NN6
KK $*WSX"Y"Y"YZZZZZrK   r   c                 F   t                      dz  } t          j                            d          rpt	          j                    dz  dz                      d          }	 |                     d          }n# t          $ r | }Y nw xY w||k    rt          d|  d          | S )N	auth.jsonPYTEST_CURRENT_TEST.hermesFstrictz8Refusing to touch real user auth store during test run: zq. Set HERMES_HOME to a tmp_path in your test fixture, or run via scripts/run_tests.sh for hermetic CI-parity env.)	r   r  environr6  r   homeresolver   RuntimeError)pathreal_home_authresolveds      rL   _auth_file_pathr  o  s    {*D 
z~~+,, )++	1K?HHPUHVV	||5|11HH 	 	 	HHH	~%%G4 G G G  
 Ks   A6 6BBOptional[Path]c                    	 ddl m}   |             }n# t          $ r Y dS w xY wt                      }	 |                    d          |                    d          k    rdS n# t          $ r ||k    rY dS Y nw xY w|dz  S )a  Return the global-root auth.json when the process is in profile mode.

    Returns ``None`` when the profile and global root resolve to the same
    directory (classic mode, or custom HERMES_HOME that is not a profile).
    Used by read-only fallback paths so providers authed at the root are
    visible to profile processes that haven't configured them locally.

    See issue #18594 follow-up (credential_pool shadowing).
    r   get_default_hermes_rootNFr  r  )hermes_constantsr  r   r   r  )r  global_rootprofile_homes      rL   _global_auth_file_pathr    s    <<<<<<--//   tt"$$Lu--1D1DE1D1R1RRR4 S   ;&&44 '& $$s    
!!.A$ $A:9A:r?   c                    t                      } | |                                 si S t          j                            d          ryt          j                            dd          }|rWt          |          dz  dz  }	 |                     d          |                    d          k    ri S n# t          $ r Y nw xY w	 t          |           S # t          $ r i cY S w xY w)	a8  Load the global-root auth store (read-only fallback).

    Returns an empty dict when no global fallback exists (classic mode,
    or the global auth.json is absent). Never raises on missing file.

    Seat belt: under pytest, refuses to read the real user's
    ``~/.hermes/auth.json`` even when HERMES_HOME is set to a profile
    path. The hermetic conftest does not redirect ``HOME``, so
    ``get_default_hermes_root()`` for a profile-shaped HERMES_HOME can
    still resolve to the real user's home on a dev machine. That would
    leak real credentials into tests. This guard uses the unmodified
    ``HOME`` env var (what ``os.path.expanduser('~')`` would resolve to),
    not ``Path.home()``, because ``Path.home`` is sometimes monkeypatched
    by fixtures that want to relocate the global root to a tmp path.
    Nr  HOMEr9   r  r  Fr  )	r  existsr  r  r6  r   r  r   r4  )global_pathreal_home_env	real_roots      rL   _load_global_auth_storer    s     )**K+"4"4"6"6		z~~+,, 
vr22 	]++i7+EI&&e&44	8I8IQV8I8W8WWWI X   ,,,    			s$   >/B/ /
B<;B< C CCc                 D    t                                          d          S )N.lock)r  with_suffixrA   rK   rL   _auth_lock_pathr    s    ((111rK   	lock_pathholderthreading.localtimeout_secondstimeout_messagec              #    K   t          |dd          dk    r=|xj        dz  c_        	 dV  |xj        dz  c_        n# |xj        dz  c_        w xY wdS | j                            dd           t          )t
          "d|_        	 dV  d|_        n# d|_        w xY wdS t
          rH|                                 r|                                 j        dk    r| 	                    dd	           | 
                    t
          rd
ndd	          5 }t          j                    t          d|          z   }	 	 t          r?t	          j        |                                t          j        t          j        z             nG|                    d           t          j        |                                t
          j        d           nX# t*          t,          t.          f$ r= t          j                    |k    rt1          |          t          j        d           Y nw xY wd|_        	 dV  d|_        t          rL	 t	          j        |                                t          j                   nD# t,          t6          f$ r Y n0w xY wt
          r`	 |                    d           t          j        |                                t
          j        d           n# t,          t6          f$ r Y nw xY wn# d|_        t          rJ	 t	          j        |                                t          j                   w # t,          t6          f$ r Y w w xY wt
          r`	 |                    d           t          j        |                                t
          j        d           w # t,          t6          f$ r Y w w xY ww xY wddd           dS # 1 swxY w Y   dS )u  Cross-process advisory flock helper.

    Reentrant per-thread via ``holder.depth``. Falls back to a depth-only
    guard when neither ``fcntl`` nor ``msvcrt`` is available (rare).
    Callers supply their own ``threading.local`` so independent locks
    (e.g. profile auth.json vs shared Nous store) don't share reentrancy
    state — that would let one lock's reentrant acquisition silently skip
    the other's kernel-level flock.
    depthr   r#   NTparentsexist_okr.   ry  encodingzr+za+      ?g?)r  r  parentmkdirfcntlmsvcrtr  statst_size
write_textopentime	monotonicmaxflockfilenoLOCK_EXLOCK_NBseeklockingLK_NBLCKBlockingIOErrorOSErrorPermissionErrorTimeoutErrorsleepLOCK_UNIOErrorLK_UNLCK)r  r  r  r  	lock_filedeadlines         rL   
_file_lockr    s3       vw""Q&&	EEELLALLLFLLALLLLLL4$777}	EEEFLL1FL  4y'')) 4Y^^-=-=-E-J-JS7333	0D7	C	C y>##c#&?&??	!
! KK	 0 0 2 2EMEM4QRRRRNN1%%%N9#3#3#5#5vJJJ#Wo> ! ! !>##x//&777
4     !	! 	EEEFL 
K	 0 0 2 2EMBBBB)   D NN1%%%N9#3#3#5#5vJJJJ)   D	 FL 
K	 0 0 2 2EMBBBB)   D NN1%%%N9#3#3#5#5vJJJJ)   D	3                 s   > AB 	B&N<7BGN<AHN<HN<%K+)N<81I+)N<+J <N<?J  
N<AKN<K'$N<&K''N<+N,;1L-,N,-M	>N, M	
N,ANN,N(	%N,'N(	(N,,N<<O O c              #     K   t          t                      t          | d          5  dV  ddd           dS # 1 swxY w Y   dS )a  Cross-process advisory lock for auth.json reads+writes.  Reentrant.

    Lock ordering invariant: when this lock is held together with
    ``_nous_shared_store_lock``, acquire ``_auth_store_lock`` FIRST
    (outer) and the shared Nous lock SECOND (inner). All runtime
    refresh paths follow this order; violating it risks deadlock
    against a concurrent import on the shared store.
    z%Timed out waiting for auth store lockN)r  r  _auth_lock_holderr  s    rL   _auth_store_lockr    s       
/	
 
   	                 s   8<<	auth_filec                   | pt                      } |                                 s
t          i dS 	 t          j        |                                           }nz# t          $ rm}|                     d          }	 dd l} |j	        | |           n# t          $ r Y nw xY wt                              d| ||           t          i dcY d }~S d }~ww xY wt          |t                    rt          |                    d          t                    s(t          |                    d          t                    rU|                    di            t          |                    d          t                    rt!          |d                    |S t          |t                    rPt          |                    d          t                    r(|d         }i }d|v r|d         |d	<   t          ||rd	nd d
S t          i dS )N)version	providersz.json.corruptr   uY   auth: failed to parse %s (%s) — starting with empty store. Corrupt file preserved at %sr  credential_poolsystemsnous_portalrM   )r  r  active_provider)r  r  AUTH_STORE_VERSIONr"  loads	read_textr   r  shutilcopy2r   r   r   rJ   r6  
setdefault_migrate_stale_nous_portal_url)r  rb  r  corrupt_pathr  r  r  s          rL   r4  r4  *  s?   ._..I @-B???@j,,..// @ @ @ ,,_==	MMMFLL1111 	 	 	D	+sL	
 	
 	

 .B????????@ #t 377;''..cgg/00$77 	{B'''cggk**D11 	=*3{+;<<<
 #t BCGGI,>,>!E!E Bi.	G## ' 6If-I-6#@66DB B 	B *;;;sA   &A 
C!C	7BC	
BC	B)C	C	Cr=  target_pathc                   ||nt                      }|j                            dd           t          |           t          | d<   t          j        t          j                  	                                | d<   t          j        | d          dz   }|                    |j         dt          j                     d	t!          j                    j                   }	 t          j        t)          |          t          j        t          j        z  t          j        z  t0          j        t0          j        z            }t          j        |d
d          5 }|                    |           |                                 t          j        |                                           d d d            n# 1 swxY w Y   tA          ||           	 t          j        t)          |j                  t          j!                  }n# tD          $ r d }Y nw xY w|C	 t          j        |           t          j#        |           n# t          j#        |           w xY w	 |$                                r|%                                 nO# tD          $ r Y nCw xY w# 	 |$                                r|%                                 w w # tD          $ r Y w w xY wxY w	 |&                    t0          j        t0          j        z             n# tD          $ r Y nw xY w|S )NTr  r  
updated_at   )indent
.tmp..wry  r  )'r  r  r  r   r  r   nowr   utc	isoformatr"  r  	with_namer7   r  getpiduuiduuid4hexr  r5   O_WRONLYO_CREATO_EXCLr  S_IRUSRS_IWUSRfdopenwriteflushfsyncr  r   O_RDONLYr  closer  unlinkchmod)r=  r  r  r  tmp_pathfdhandledir_fds           rL   _save_auth_storer  T  s(     +6O<M<MI4$777 i   .Jy'|HL99CCEEJ|jA...5G""in#[#[29;;#[#[IY#[#[\\H
 WMMK"*$ry0L4<'
 

 Yr3111 	&VLL!!!LLNNNHV]]__%%%	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	x+++	WS!122BK@@FF 	 	 	FFF	!           	   "!!! 	 	 	D		   "!!!!" 	 	 	D	t|34444   s   A5J AF/#J /F33J 6F37J 1G= <J =H	J HJ H; &J ;IJ (I> >
J
JK
(J:8K
:
KK
KK
,K; ;
LL/tuple[Optional[Dict[str, Any]], Optional[Path]]c                   |                      d          }t          |t                    rG|                     |          }t          |t                    rt          |          t                      fS t	                      }t                      }|re|                     d          }t          |t                    r;|                     |          }t          |t                    rt          |          |fS dS )a  Return a provider state plus the auth.json path it came from.

    Most callers only need the state, but refresh paths that rotate single-use
    OAuth refresh tokens must write the updated token chain back to the same
    store they read. In profile mode ``_load_provider_state`` can read a
    global-root fallback state; persisting a rotated Nous refresh token only to
    the profile would leave the global/root store stale and cause the next
    process to replay an already-consumed refresh token.
    r  NN)r6  r   rJ   r  r  r  )r=  r   r  r>  r  global_storeglobal_providersglobal_states           rL    _load_provider_state_with_sourcer    s     {++I)T"" 2k**eT"" 	2;; 1 111(**K*,,L 7'++K88&-- 	7+//<<L,-- 7L));66:rK   Optional[Dict[str, Any]]c                ,    t          | |          \  }}|S )a  Return a provider's persisted state.

    In profile mode, falls back to the global-root ``auth.json`` when the
    profile has no entry for ``provider_id``. This mirrors the per-provider
    shadowing already used by ``read_credential_pool``: workers spawned in a
    profile can see providers (e.g. ``nous``) that were only authenticated at
    global scope. Once the user runs ``hermes auth login <provider>`` inside
    the profile, the profile state fully shadows the global state on the next
    read. See issue #18594 follow-up.
    )r  )r=  r   r>  _source_paths       rL   r5  r5    s     ;:{SSE<LrK   r>  c                    |                      di           }t          |t                    si | d<   | d         }|||<   || d<   d S Nr  r  r  r   rJ   )r=  r   r>  r  s       rL   r;  r;    sX    %%k266Ii&& ,"$
;{+	"Ik$/J !!!rK   source_pathc                `   t                      }||}	 |                    d          |                    d          k    }n# t          $ r	 ||k    }Y nw xY w|r"t          | ||           t	          |            dS t          |          }t          |||           t	          ||           dS )z?Persist provider state back to the auth store it was read from.NFr  )r  )r  r  r   r;  r  r4  )r=  r   r>  r  active_path
same_storesource_stores          rL   _save_provider_state_to_sourcer#    s     "##K!0 (((66+:M:MUZ:M:[:[[

 0 0 0 K/


0 Ze<<<$$$#K00L{E:::\{;;;;;;s   .A AAT
set_activer%  c                   |                      di           }t          |t                    si | d<   | d         }|||<   |r|| d<   d S d S r  r  )r=  r   r>  r%  r  s        rL   _store_provider_stater'    sl     %%k266Ii&& ,"$
;{+	"Ik 4(3
$%%%4 4rK   c                    t                      5  t                      }|                    d          pd                                s| |d<   t	          |           ddd           dS # 1 swxY w Y   dS )a5  Set ``active_provider`` to *provider_id* only when none is set yet.

    Used by ``hermes auth add`` OAuth paths that create credential-pool
    entries directly (no singleton ``providers.<id>`` block). Adding the
    very first credential for a provider should make it the active provider
    so the setup wizard's ``_model_section_has_credentials()`` check (which
    consults ``get_active_provider()``) does not report "No inference
    provider configured". Subsequent adds for an already-active setup leave
    the user's chosen active provider untouched.
    r  r9   N)r  r4  r6  r   r  r   r=  s     rL   mark_provider_active_if_unsetr*    s     
		 ) )%''
0117R>>@@ 	),7J()Z(((	) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )s   AA((A,/A,c                v    | pd                                                                 }|t          v p|t          v S Nr9   )r   r   r   r2   r   
normalizeds     rL   is_known_auth_providerr/    s;    #**,,2244J**Rj<R.RRrK   c                    | pd                                                                 }|t          v rt          |         j        S t                              ||           S r,  )r   r   r   r7   r2   r6  r-  s     rL   get_auth_provider_display_namer1    sT    #**,,2244J&&& ,11!%%j+>>>rK   c                   t                      }|                    d          }t          |t                    si }i }t	                      }|r|                    d          nd}t          |t                    r|}| t          |          }|                                D ]\\  }}t          |t                    r|s|                    |          }	t          |	t                    r|	rJt          |          ||<   ]|S |                    |           }
t          |
t                    r|
rt          |
          S |                    |           }t          |t                    rt          |          ng S )u  Return the persisted credential pool, or one provider slice.

    In profile mode, the profile's credential pool is authoritative. If a
    provider has no entries in the profile, entries from the global-root
    ``auth.json`` are used as a read-only fallback — so workers spawned in a
    profile can see providers that were only authenticated at global scope.

    Profile entries always win: the global fallback only applies per-provider
    when the profile has zero entries for that provider. Once the user runs
    ``hermes auth add <provider>`` inside the profile, profile entries
    fully shadow global for that provider on the next read.

    Writes always go to the profile (``write_credential_pool`` is unchanged).
    See issue #18594 follow-up.
    r  N)r4  r6  r   rJ   r  itemslist)r   r=  r	  global_poolr  maybe_global_poolmergedgp_key
gp_entriesexistingprovider_entriesglobal_entriess               rL   read_credential_poolr=    s     "##J>>+,,DdD!! "$K*,,L?KU(():;;;QU#T** ('d"-"3"3"5"5 	. 	.FJj$// z zz&))H(D)) h !*--F6NNxx,,"D)) &.> &$%%% __[11N#-nd#C#CK4KrK   )removed_idsentriesList[Dict[str, Any]]r>  Optional[Iterable[str]]c                   d |pdD             }t                      5  t                      }|                    d          }t          |t                    si }||d<    fd|D             }|                               }t          |t
                    r|ng }d |D             }	t          |          }
|D ][}t          |t                    s|                    d          }|r||	v s||v r8|
                    t          |                      \|
| <   t          |          cddd           S # 1 swxY w Y   dS )a  Persist one provider's credential pool under auth.json.

    This is the final disk-boundary guard for borrowed/reference-only
    credentials. Callers may pass raw dictionaries, so sanitize here even when
    ``PooledCredential.to_dict()`` already did the same work upstream.

    Re-read the on-disk pool under the same lock and merge entries present on
    disk but missing from ``entries``. Those were added by another process after
    the caller loaded its in-memory snapshot; without this merge a later
    rotation/exhaustion rewrite drops the concurrent credential.

    Pass ``removed_ids`` for entries the caller intentionally removed, so the
    merge does not resurrect them from the on-disk copy.
    c                    h | ]}||S rA   rA   )r   rids     rL   	<setcomp>z(write_credential_pool.<locals>.<setcomp>I  s    999sS9s999rK   rA   r  c                ^    g | ])}t          |t                    rt          |          n|*S rA   )r   rJ   r   )r   r
  r   s     rL   
<listcomp>z)write_credential_pool.<locals>.<listcomp>P  sM     
 
 
  %&&20DDD,1
 
 
rK   c                    h | ]A}t          |t                    |                    d           ,|                    d           BS )r6   )r   rJ   r6  )r   r
  s     rL   rE  z(write_credential_pool.<locals>.<setcomp>W  sV     
 
 
%&&
 ,199T??
IIdOO
 
 
rK   r6   N)	r  r4  r6  r   rJ   r4  appendr   r  )r   r?  r>  removedr=  r	  sanitized_entriesr:  existing_listnew_idsr7  
disk_entrydisk_ids   `            rL   write_credential_poolrP  5  s   ( :9{0b999G			 , ,%''
~~/00$%% 	1D,0J()
 
 
 
 !
 
 

 88K(($.x$>$>FB
 
*
 
 

 (,,='>'>' 	Y 	YJj$//  nnT**G g00Gw4F4FMM>z;WWXXXX"[
++7, , , , , , , , , , , , , , , , , ,s   DD33D7:D7r  c                   t                      5  t                      }|                    di           }|                    | g           }||vr|                    |           t	          |           ddd           dS # 1 swxY w Y   dS )z@Mark a credential source as suppressed so it won't be re-seeded.suppressed_sourcesN)r  r4  r  rI  r  r   r  r=  
suppressedprovider_lists        rL   suppress_credential_sourcerV  h  s    			 % %%''
**+?DD
"--k2>>&&  ((($$$% % % % % % % % % % % % % % % % % %s   A#A??BBc                    	 t                      }|                    di           }||                    | g           v S # t          $ r Y dS w xY w)z=Check if a credential source has been suppressed by the user.rR  F)r4  r6  r   )r   r  r=  rT  s       rL   is_source_suppressedrX  s  sa    %''
^^$8"==
R8888   uus   ;> 
AAc                   t                      5  t                      }|                    d          }t          |t                    s	 ddd           dS |                    |           }t          |t
                    r||vr	 ddd           dS |                    |           |s|                    | d           |s|                    dd           t          |           	 ddd           dS # 1 swxY w Y   dS )zClear a suppression marker so the source will be re-seeded on the next load.

    Returns True if a marker was cleared, False if no marker existed.
    rR  NFT)	r  r4  r6  r   rJ   r4  removepopr  rS  s        rL   unsuppress_credential_sourcer\  }  s   
 
		  %''
^^$899
*d++ 		       
 #{33-.. 	&2M2M        	V$$$ 	.NN;--- 	7NN/666$$$                 s   :C4/C4AC44C8;C8c                >    t                      }t          ||           S )u  Return persisted auth state for a provider, or None.

    In profile mode, ``_load_provider_state`` already falls back to the
    global-root ``auth.json`` per-provider when the profile has no entry —
    so this is now a thin convenience wrapper. Profile state always wins
    when present. Writes (``_save_auth_store`` / ``persist_*_credentials``)
    are unchanged — they still target the profile only. This mirrors
    ``read_credential_pool``'s per-provider shadowing semantics so that
    ``_seed_from_singletons`` can reseed a profile's credential pool from
    global-scope provider state (e.g. a globally-authenticated Anthropic
    OAuth or Nous device-code session). See issue #18594 follow-up.
    )r4  r5  r)  s     rL   get_provider_auth_stater^    s     "##J
K888rK   c                 H    t                      } |                     d          S )z8Return the currently active provider ID from auth store.r  )r4  r6  r=  s    rL   get_active_providerra    s     !##J>>+,,,rK   c                   | pd                                                                 }	 t                      }|                    d          pd                                                                 }|r||k    rdS n# t          $ r Y nw xY w	 ddlm}  |            }|                    d          }t          |t                    rC|                    d          pd                                                                 }||k    rdS n# t          $ r Y nw xY wdh}t                              |          }	|	r?|	j
        d	k    r4|	j        D ],}
|
|v rt          t          j        |
d                    r dS -d
S )a  Return True only if the user has explicitly configured this provider.

    Checks:
      1. active_provider in auth.json matches
      2. model.provider in config.yaml matches
      3. Provider-specific env vars are set (e.g. ANTHROPIC_API_KEY)

    This is used to gate auto-discovery of external credentials (e.g.
    Claude Code's ~/.claude/.credentials.json) so they are never used
    without the user's explicit choice.  See PR #4210 for the same
    pattern applied to the setup wizard gate.
    r9   r  Tr   load_configr  rM  r   rQ   F)r   r   r4  r6  r   r   rd  r   rJ   r   r8   rC   r   r  r  )r   r.  r=  activerd  cfg	model_cfgcfg_provider_IMPLICIT_ENV_VARSr   r  s              rL   !is_provider_explicitly_configuredrj    s    #**,,2244J%''
..!2339r@@BBHHJJ 	f
**4   	111111kmmGGG$$	i&& 	%MM*55;BBDDJJLLLz))t    44##J//G 7$	11/ 	 	G,,, 7B!7!788 tt 5s%   AA> >
B
BA;D 
DDc                (   t                      5  t                      }| p|                    d          }|s	 ddd           dS |                    di           }t          |t                    si }||d<   |                    d          }t          |t                    si }||d<   d}||v r||= d}||v r||= d}|                    d          |k    rd|d<   d}|s	 ddd           dS t          |           ddd           n# 1 swxY w Y   dS )z
    Clear auth state for a provider. Used by `hermes logout`.
    If provider_id is None, clears the active provider.
    Returns True if something was cleared.
    r  NFr  r  T)r  r4  r6  r   rJ   r  )r   r=  targetr  r	  cleareds         rL   clear_provider_authrn    s    
		 % %%''
A
/@ A A 		% % % % % % % % NN;33	)T** 	0I&/J{#~~/00$%% 	1D,0J()Y&!GT>>VG>>+,,66,0J()G 	;% % % % % % % %< 	$$$=% % % % % % % % % % % % % % %> 4s   )DBD,DDDc                     t                      5  t                      } d| d<   t          |            ddd           dS # 1 swxY w Y   dS )z
    Clear active_provider in auth.json without deleting credentials.
    Used when the user switches to a non-OAuth provider (OpenRouter, custom)
    so auto-resolution doesn't keep picking the OAuth provider.
    Nr  )r  r4  r  r`  s    rL   deactivate_providerrp    s     
		 % %%''
(,
$%$$$% % % % % % % % % % % % % % % % % %s   #?AAprovider_namec                j   	 ddl m}  |            }|sdS dg}|D ]s}|j        dk    rdnd}|                    d| d	|j                    |j        r|j                                        d         nd}|r|                    d
|            td                    |          S # t          $ r Y dS w xY w)zReturn a helpful hint string when provider resolution fails.

    Checks for common config.yaml mistakes (malformed custom_providers, etc.)
    and returns a human-readable diagnostic, or empty string if nothing found.
    r   )validate_config_structurer9   uC   Config issue detected — run 'hermes doctor' for full diagnostics:rZ  ERRORWARNINGz  [z] u       → r  )	r   rs  severityrI  rP  hint
splitlinesjoinr   )rq  rs  issueslinesciprefix
first_hints          rL   %_get_config_hint_for_unknown_providerr    s   ??????**,, 	2VW 	6 	6B "w 6 6WWIFLL5v555566646GC++--a00J 64
44555yy   rrs   B$ BB$ $
B21B2)explicit_api_keyexplicit_base_url	requestedr  r  c          	     	   | pd                                                                 }i ddddddddddd	dd
dddddddddddddddddddddi ddddddddddddd d!d"d!d#d$d%d$d&d'd(d'd)d'd*d+d,d+d-d+d.d/i d0d/d1d2d3d2d4d2d5d2d6d7d8d7d9d:d;d:d<d=d>d=d=d=d?d@dAd@dBd@dCdDdEdDi dFdGdHdGdIdGdJdGdKdLdMdLdNdLdOdLdPdQdRdQdSdTdUdTdVdTdWdWdXdWdYdWdZd[d\d[d[d[d[d]}	 d^d_lm}  |            D ]}|j        D ]}||vr
|j        ||<   n# t          $ r Y nw xY w|                    ||          }|d`k    rd`S |d[k    rd[S |t          v r|S |dk    r6t          |          }da| db}	|r	|	dc| z  }	n|	ddz  }	t          |	def          |s|rd`S dg}
	 d^dhlm}  |            pi                     di          }
t          |
t                    r}|
                    dj          }t          |t                    rS|                                                                 t          v r&|                                                                 S n2# t          $ r%}t                               dk|           Y dg}~ndg}~ww xY wt%          t'          j        dl                    s!t%          t'          j        dm                    rd`S 	 d^dnlm}  |d`                                          rd`S n2# t          $ r%}t                               do|           Y dg}~ndg}~ww xY wdg}	 t1                      }|                    dp          }|r-|t          v r$t3          |                              dq          r|}n2# t          $ r%}t                               dr|           Y dg}~ndg}~ww xY wt                                          D ]n\  }}|j        dsk    r|dtv r|j        D ]P}t%          t'          j        |du                    r,|r$||k    rt                               dv||||           |c c S Qo|rIt          |
t                    r2|
r0|
                    dj          st                               dw|           |S 	 d^dxlm}  |            rdLS n# t@          $ r Y nw xY wt          dydzf          ){uX  
    Determine which inference provider to use.

    Priority (when requested="auto" or None) — explicit user intent wins over a
    stale logged-in OAuth provider (#29285):
    1. Explicit CLI api_key/base_url -> "openrouter"
    2. config.yaml `model.provider`
    3. OPENAI_API_KEY / OPENROUTER_API_KEY env vars -> "openrouter"
    4. OpenRouter credential pool
    5. Provider-specific API keys (GLM, Kimi, MiniMax, ...) -> that provider
    6. auth.json `active_provider` (logged-in OAuth) — last-resort fallback
    7. AWS Bedrock credential chain
    8. Error (no provider configured)
    autoglmre   zz-aizz.aizhipugooglera   zgoogle-geminizgoogle-ai-studiozx-air   x.aigrokrT   z
x-ai-oauthz
grok-oauthzxai-grok-oauthkimirj   zkimi-for-codingmoonshotzkimi-cnrn   zmoonshot-cnsteprp   zstepfun-coding-planzarcee-airs   arceeaiz	gmi-cloudrv   gmicloudzminimax-chinar   
minimax_cnzminimax-portalr}   zminimax-globalminimax_oauthalibaba_codingr   zalibaba-codingalibaba_coding_planclauder   zclaude-codegithubrY   zgithub-copilotzgithub-modelszgithub-modelzgithub-copilot-acpr^   zcopilot-acp-agentopencoder   zenzqwen-portalrU   qwen-clihfr   zhugging-facezhuggingface-hubmimor   zxiaomi-mimotencentr   tokenhubztencent-cloudtencentmaasawsr   zaws-bedrockzamazon-bedrockamazongor   zopencode-go-subkilor   z	kilo-codezkilo-gatewayrV   z	lm-studio	lm_studioollamar   r   )ollama_cloudvllmllamacppz	llama.cppz	llama-cppr   r   r   zUnknown provider 'z'.z

z` Check 'hermes model' for available providers, or run 'hermes doctor' to diagnose config issues.invalid_provider)rN  Nrc  r  rM  zACould not read config.yaml model.provider for auto-resolution: %srR   OPENROUTER_API_KEYr   z.Could not check OpenRouter credential pool: %sr  	logged_inz+Could not pre-read active auth provider: %srQ   >   rY   rV   r9   zProvider resolved to %r via %s, preempting your logged-in OAuth provider %r. If you meant to use the OAuth login, unset %s or set `model.provider` explicitly.zProvider resolved to logged-in OAuth provider %r because config.yaml `model` has no `provider` key. If you meant a different provider, set `model.provider` explicitly.has_aws_credentialszNo inference provider configured. Run 'hermes model' to choose a provider and model, or set an API key (OPENROUTER_API_KEY, OPENAI_API_KEY, etc.) in ~/.hermes/.env.no_provider_configured)!r   r   r  r   aliasesr7   r   r6  r   r  rK  r   rd  r   rJ   r5   r   r*  r   r  r  r   r   r   r4  get_auth_statusr3  r8   rC   r   agent.bedrock_adapterr  ImportError)r  r  r  r.  _PROVIDER_ALIASES_lp_pp_alias_config_hintmsg
_model_cfgrd  _cfg_providere
_load_pool_oauth_active_store_maybepidr   r  r  s                         rL   resolve_providerr  /  s   ( %v,,..4466J!u!e!%+U!4;U!(!+X!7I8! 	! u! '-e! 	[	! #/	!
 	k!
 $4[! 	!  1-! BL]! ! 	#! &34D! 		! 1)! 	G! '! 	U! '! 	! (4\! 	/! ,<_! O^_n! 	/! 2BCX! 	4! 	+! ! !  -k!  	)!!  .y!!" 	#!" %3I#!$ 	m%!$ .A-%!& 	N'!& %*>'!( 	|)!( &0)!( @L\)!* 	m+!* ,]+!* =N}+!, 	-!, (-! ! !. 	%/!. (23E/!0 	+1!0 .;<N1!2 	y3!2 (3!2 5Ei3!2 RZ[d3!4 	m5!4 /5!6 	
7!6 (7!6 6DZ7!8 	J9!8 !,Z9!8 :Ej9!< 	(=! !< -;hHA! ! !J333333355 	9 	9C+ 9 9!22203%f-9	9    "&&z:>>J\!!|Xx&&&V<ZHH1:111 	v(,(((CCuuC"45555  , | J	]111111!kmm)r..w77
j$'' 	5&NN:66M--- 5-2E2E2G2G2M2M2O2OSd2d2d$**,,22444 ] ] ]XZ[\\\\\\\\] #34455 9J29UiKjKj9k9k |JAAAAAA:l##3355 	 <	  J J JEqIIIIIIIIJ $(MG!##-.. 	#f 111of6M6M6Q6QR]6^6^1"M G G GBAFFFFFFFFG *//11  W	)) )))/ 	 	G 7B!7!788 
 ! ]c%9%9NN& WmW   




	*  
 j$'' 	J 	z~~j?Y?Y 	NNG 	   ======   	9	    	3 &	   sn   +D2 2
D?>D?8B6I0 0
J:JJ'#L 
L<L77L<AN 
ON??OR. .
R;:R;Optional[float]c                l   t          | t                    r| sd S |                                 }|sd S |                    d          r|d d         dz   }	 t	          j        |          }n# t          $ r Y d S w xY w|j         |                    t          j
                  }|                                S )NZ+00:00)tzinfo)r   r5   r   r   r   fromisoformatr   r  replacer   r  	timestamp)r   textparseds      rL   _parse_iso_timestampr    s    eS!!  t;;==D t}}S $CRCy8#'--   tt}x|44s   A* *
A87A8expires_at_isoskew_secondsc                \    t          |           }|dS |t          j                    |z   k    S )NT)r  r  )r  r  expires_epochs      rL   _is_expiringr    s0    (88MtTY[[<788rK   
expires_inc                h    	 t          |           }n# t          $ r d}Y nw xY wt          d|          S )Nr   )r   r   r  )r  ttls     rL   _coerce_ttl_secondsr    sF    *oo   q#;;s    !!c                    t          | t                    sd S |                                                     d          }|r|nd S )NrC  )r   r5   r   rE  )r   r   s     rL   _optional_base_urlr    sC    eS!! tkkmm""3''G'774'rK   zapi.nousresearch.comzFrozenSet[str]_NOUS_STALE_PORTAL_HOSTS>   portal.nousresearch.com	localhost	127.0.0.1_NOUS_PORTAL_ALLOWED_HOSTSr  c                L   |                      d          }t          |t                    sd S |                     d          pd                                }|rJt	          |          }|j        t          v r/t                              d|t                     t          |d<   d S d S d S )NrM   r:   r9   z3auth: migrating stale nous portal_base_url %s -> %s)
r6  r   rJ   r   r   hostnamer  r   r   DEFAULT_NOUS_PORTAL_URL)r  rM   storedr  s       rL   r  r  3  s    ==  DdD!! hh())/R6688F >&!!?666NNE/   '>D"###> >66rK   zinference-api.nousresearch.com_ALLOWED_NOUS_INFERENCE_HOSTSurlc                   t          | t                    sdS |                                 }|sdS 	 t          |          }n# t          $ r Y dS w xY w|j        dk    r"t                              d|j                   dS |j        t          vr"t                              d|j                   dS |
                    d          S )u  Validate a Portal-returned inference URL against the host allowlist.

    Returns ``url`` (normalised by stripping trailing slashes) if it's a
    well-formed ``https://<allowlisted-host>/...`` URL. Returns ``None``
    if the URL is missing, malformed, non-https, or points at an
    unexpected host — letting the caller fall back to the configured
    default rather than persist or forward a poisoned value.

    Defense-in-depth: a compromised refresh response from the Portal API
    (MITM, malicious response injection) could otherwise redirect every
    subsequent proxy request — bearing the user's inference JWT — to an
    attacker-controlled endpoint.
    Validating scheme + host at the source closes that loop before the
    poisoned URL ever lands in ``auth.json``.

    The env-var override path (``NOUS_INFERENCE_BASE_URL``) bypasses
    this — env values come from the trusted OS user, not from the
    network, and the override is documented for staging/dev use.

    Co-authored-by: memosr <mehmet.sr35@gmail.com>
    NhttpszEnous: refusing non-https inference URL scheme %r from Portal responsezenous: refusing inference URL host %r from Portal response (not in allowlist); falling back to defaultrC  )r   r5   r   r   r   schemer   r   r  r  rE  )r  r   r  s      rL   )_validate_nous_inference_url_from_networkr  O  s    , c3 tiikkG t'""   tt}SM	
 	
 	
 t;;;:O	
 	
 	

 t>>#s   A 
AAc                 D    t          t          j        d                    S )u  Return the user-set ``NOUS_INFERENCE_BASE_URL`` override, if any.

    This is the documented dev/staging escape hatch. The env source is
    trusted (the OS user set it themselves), so it is intentionally NOT
    gated by the network host allowlist — unlike Portal-returned URLs.

    Returns a trailing-slash-stripped non-empty string, or ``None`` when
    the env var is unset/blank.
    NOUS_INFERENCE_BASE_URLr  r  r  rA   rK   rL   _nous_inference_env_overrider  ~  s     bi(ABBCCCrK   c                 l    t          t          j        d          pt          j        d                    S )u  Return the user/deployment-set Portal base URL override, if any.

    Mirrors ``_nous_inference_env_override()``: ``HERMES_PORTAL_BASE_URL`` /
    ``NOUS_PORTAL_BASE_URL`` are the documented dev/staging escape hatch for
    pointing Hermes at a non-production Nous Portal (e.g. a hosted agent
    provisioned on nous-account-service's `staging` environment, which stamps
    ``HERMES_PORTAL_BASE_URL=https://portal.staging-nousresearch.com`` into
    the container env). The env source is trusted (the OS user/deployment
    set it themselves), so — like the inference override — it must NOT be
    gated by ``_NOUS_PORTAL_ALLOWED_HOSTS``: that allowlist exists to reject
    an untrusted NETWORK-provided value (a poisoned portal_base_url
    persisted to auth.json), not a value the operator explicitly configured.

    Returns a trailing-slash-stripped non-empty string, or ``None`` when
    neither env var is set/blank.
    HERMES_PORTAL_BASE_URLNOUS_PORTAL_BASE_URLr  rA   rK   rL   _nous_portal_env_overrider    s4    " 
	*++Pry9O/P/P  rK   c                   t          | t                    r|                     d          dk    ri S |                     d          d         }|ddt	          |          dz  z
  dz  z  z  }	 t          j        |                    d                    }t          j	        |
                    d                    }n# t          $ r i cY S w xY wt          |t                    r|ni S )Nr  r  r#   =r   ry  )r   r5   countsplitr   base64urlsafe_b64decoder9  r"  r  decoder   rJ   )r  r  rb  claimss       rL   _decode_jwt_claimsr    s    eS!! U[[%5%5%:%:	kk#q!Gsq3w<<!++q011G&w~~g'>'>??CJJw//00   			--56625s   +AB: :C	C		raw_scopeset[str]c                   t                      }t          | t                    rW|                     dd                                          D ]-}|                                }|r|                    |           .ndt          | t          t          t           t          f          r<| D ]9}t          |t                    r"|
                    t          |                     :|S )N,r.   )setr   r5   r  r  r   addr4  rB   	frozensetr  _scope_values)r  scopespartr   items        rL   r  r    s     uuF)S!! 3%%c3//5577 	$ 	$DjjllG $

7###	$ 
IeS)<	=	= 3 	3 	3D$$$ 3mD11222MrK   r=   
expires_atmin_ttl_secondsr=   r  r  c                  t          |           }|sdS t          |          t          |                    d                    z  t          |                    d                    z  }t          |vrdS |                    d          }t	          dt          |                    }t          |t
          t          f          r+t          |          t          j                    |z   k    rdS dS t          ||          rd	S dS )
zDReturn None when the token can be used for inference, else a reason.access_token_not_jwtr=   scpmissing_inference_invoke_scopeexpr   invoke_jwt_expiringN%invoke_jwt_expiry_unknown_or_expiring)
r  r  r6  NOUS_INFERENCE_INVOKE_SCOPEr  r   r   r  r  r  )r  r=   r  r  r  r  r  skews           rL   _nous_invoke_jwt_statusr    s      &&F &%%e


7++
,
,	-


5))
*
*	+ 
 #&00//
**U

Cq#o&&''D#U|$$ ::$)++,--((tJ%% 7664rK   c               ,    t          | |||          d u S )Nr  )r  )r  r=   r  r  s       rL   _nous_invoke_jwt_is_usabler    s1     	 !+		
 	
 	
 	rK   r   r   c                   ||                      d          n|}t          ||                      d          |                      d                    }|d S t          d| dd|d	          )
Nr   r=   r  r=   r  8Nous Portal access token is not a usable inference JWT (z-). Re-authenticate with: hermes auth add nousrM   TrL  )r6  r  rK  )r>  r   r  reasons       rL   !_assert_nous_inference_jwt_usabler    s    
 *6)=EIIn%%%<E$ii  99\**  F
 ~
	B	B 	B 	B   rK   c                x    t                               d           t          d|t          |                      d S )Nz)Nous inference auth: using NAS invoke JWTnous_invoke_jwt_selected)r  access_token_fp)r   r<  r  r{  r   r  s     rL   _log_nous_invoke_jwt_selectedr    sI    
 KK;<<<"*<88     rK   fallback_expires_atc                T   t          |           }|                    d          }t          |t          t          f          rP	 t          j        t	          |          t          j                  	                                S # t          $ r Y nw xY wt          |t                    r|nd S )Nr  tz)r  r6  r   r   r  r   fromtimestampr   r  r  r   r5   )r  r  r  r  s       rL   _nous_jwt_expires_atr     s    &&F
**U

C#U|$$ 	)%**FFFPPRRR 	 	 	D	",-@#"F"FPDPs   >B 
BB)obtained_atr!  c               
   |                      d          }t          |t                    r|                                sd S t	          j        t          j                  }|                      d          }|r|}nY|                      d          |k    r,t          |t                    r|                                r|}n|                                }t          ||                      d                    }t          |          }|1t          dt          |t          j                    z
                      n!t          |                      d                    }|r
|| d<   || d<   || d<   d | d<   || d<   || d	<   d
| d<   || d<   d S )Nr   agent_key_obtained_at	agent_keyr  r   r  agent_key_idagent_key_expires_atagent_key_expires_inFagent_key_reused)r6  r   r5   r   r   r  r   r  r  r   r  r  r   r  r  )	r>  r!  r   r  existing_obtained_ateffective_obtained_atr  r  r  s	            rL   #_set_nous_agent_key_from_invoke_jwtr+    s   
 99^,,LlC(( 0B0B0D0D 
,x|
$
$C 99%<== 	0 +		+,..+S11 	/ &&(( 	/ !5 #%lEIIl4K4KLLJ(44M $ 	As=49;;.//000 <!8!899 
  )(l(l%E+ E.$.E
 !$.E
 ! %E
%:E
!"""rK   r  c                   t          |t                    r|                                r|| d<   t          |            t	          |                     d          |           d S )Nr   r  )r   r5   r   r+  r  r6  )r>  r   r  s      rL   _select_nous_invoke_jwtr-  A  st     ,$$ -););)=)= - ,n'...!YY~..     rK   r'  c                >    d |                                  D             S )Nc                ,    i | ]\  }}|t           v||S rA   )"_NOUS_EFFECTIVE_STATE_IGNORED_KEYS)r   r  r   s      rL   
<dictcomp>z2_nous_effective_provider_state.<locals>.<dictcomp>Z  s4       C888 	U888rK   )r3  r>  s    rL   _nous_effective_provider_stater3  Y  s+     ++--   rK   c                
   t          |           }|                    d          }t          |t          t          f          sdS t	          |          t          j                    t          dt          |                    z   k    S )Nr  Fr   )r  r6  r   r   r  r  r  )r   r  r  r  s       rL   _codex_access_token_is_expiringr5  a  si    --F
**U

CcC<(( u::$)++As</@/@(A(AABBrK   c                 4    t          j                    dz  dz  S )Nz.qwenzoauth_creds.json)r   r  rA   rK   rL   _qwen_cli_auth_pathr7  i  s    9;; #555rK   c                 n   t                      } |                                 st          ddd          	 t          j        |                     d                    }n+# t          $ r}t          d|  d| dd	          |d }~ww xY wt          |t                    st          d
|  ddd          |S )NzAQwen CLI credentials not found. Run 'qwen auth qwen-oauth' first.rU   qwen_auth_missingrM  rN  ry  r  z)Failed to read Qwen CLI credentials from : qwen_auth_read_failedz Invalid Qwen CLI credentials in r  qwen_auth_invalid)	r7  r  rK  r"  r  r  r   r   rJ   )	auth_pathdatar  s      rL   _read_qwen_cli_tokensr@  m  s   #%%I 
O!$
 
 
 	

z)--w-??@@   J	JJSJJ!(
 
 
 		 dD!! 
;y;;;!$
 
 
 	

 Ks   (A 
B)BBtokensc                   t                      }|j                            dd           t          |           |                    |j         dt          j                     dt          j	                    j
                   }t          j        t          |          t          j        t          j        z  t          j        z  t           j        t           j        z            }	 t          j        |dd          5 }|                    t+          j        | dd	          d
z              |                                 t          j        |                                           d d d            n# 1 swxY w Y   t5          ||           	 |                                r|                                 nO# t:          $ r Y nCw xY w# 	 |                                r|                                 w w # t:          $ r Y w w xY wxY w|S )NTr  r  r  r  ry  r  r  r  r  r  )r7  r  r  r   r  r7   r  r  r  r  r  r  r5   r  r   r  r  r  r  r  r  r"  r  r  r  r  r   r  r
  r  )rA  r>  r  r  fhs        rL   _save_qwen_cli_tokensrE    s   #%%I4$777i    ""in#[#[29;;#[#[IY#[#[\\H 
H
bj 29,t|#
 
B
Yr3111 	"RHHTZqDAAADHIIIHHJJJHRYY[[!!!	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	x+++	   "!!! 	 	 	D		   "!!!!" 	 	 	D	sg   "G 9A(E-!G -E11G 4E15G 	(F2 2
F?>F?G>(G.,G>.
G;8G>:G;;G>expiry_date_msc                    	 t          |           }n# t          $ r Y dS w xY wt          j                    t          dt          |                    z   dz  |k    S )NTr     )r   r   r  r  )rF  r  	expiry_mss      rL   _qwen_access_token_is_expiringrJ    se    ''		   ttIKK#a\!2!2333t;yHHs    
        4@c                   t          |                     dd          pd                                          }|st          ddd          	 t	          j        t          ddd	d|t          d
|          }n(# t          $ r}t          d| dd          |d }~ww xY w|j	        dk    r5|j
                                        }t          d|rd| ndz   dd          	 |                                }n(# t          $ r}t          d| dd          |d }~ww xY wt          |t                    r7t          |                    dd          pd                                          st          ddd          |                    d          }	 t          |          }n# t          $ r d}Y nw xY wt          |                    dd          pd                                          t          |                    d|          p|                                          t          |                    d|                     dd                    pd                                          pdt          |                    d|                     dd                    pd                                          t          t          j                    dz            t!          d|          dz  z   d}	t#          |	           |	S )Nrefresh_tokenr9   z@Qwen OAuth refresh token missing. Re-run 'qwen auth qwen-oauth'.rU   qwen_refresh_token_missingr:  !application/x-www-form-urlencodedr  r  Accept
grant_typerM  r<   r!  r?  r  zQwen OAuth refresh failed: qwen_refresh_failed  z9Qwen OAuth refresh failed. Re-run 'qwen auth qwen-oauth'. Response: z*Qwen OAuth refresh returned invalid JSON: qwen_refresh_invalid_jsonr   z1Qwen OAuth refresh response missing access_token.qwen_refresh_invalid_responser  i`T  
token_typeBearerresource_urlzportal.qwen.airH  r#   )r   rM  rZ  r\  expiry_date)r5   r6  r   rK  r'  r(  QWEN_OAUTH_TOKEN_URLQWEN_OAUTH_CLIENT_IDr   r)  r  r"  r   rJ   r   r  r  rE  )
rA  r  rM  responser  bodyr  r  expires_in_seconds	refresheds
             rL   _refresh_qwen_cli_tokensrd    s   

?B77=2>>DDFFM 
N!-
 
 
 	
:  C, 
 .!.1 
 $
 
 
    /#//!&
 
 
 		 s""}""$$G'+3#T###5!&	
 
 
 	
--//   >>>!,
 
 
 		 gt$$ 
CNB0O0O0USU,V,V,\,\,^,^ 
?!0
 
 
 	
 \**J) __ ) ) )() GKK;;ArBBHHJJW[[-HHYMZZ``bb'++lFJJ|X4V4VWWc[cddjjllxpxGKK

>Sc8d8deeyiyzz  A  A  C  C49;;-..Q8J1K1Kd1RR I )$$$sB   )A7 7
BBB C5 5
D?DDF! !F0/F0credsc                   t                      5  t                      }i }|                     d          rt          | d                   |d<   t	          |d|           t          |           ddd           dS # 1 swxY w Y   dS )a  Set active_provider to qwen-oauth in auth.json.

    Qwen OAuth tokens live in the Qwen CLI credential file managed by
    _save_qwen_cli_tokens / resolve_qwen_runtime_credentials. This function
    only writes a minimal provider-state entry (base_url for display) and
    sets active_provider so that get_active_provider() and
    _model_section_has_credentials() detect the provider for the setup wizard
    and status commands.
    r$  rU   N)r  r4  r6  r5   r;  r  )re  r=  r>  s      rL   _mark_qwen_oauth_activerg    s     
		 % %%''
 "99Z   	7 #E*$5 6 6E*Zu===$$$% % % % % % % % % % % % % % % % % %s   AA::A>A>F)force_refreshrefresh_if_expiringrefresh_skew_secondsrh  ri  rj  c           	        t                      }t          |                    dd          pd                                          }t	          |           }|s%|r#t          |                    d          |          }|rFt          |          }t          |                    dd          pd                                          }|st          ddd          t          j	        dd                                          
                    d	          pt          }d||d
|                    d          t          t                                dS )Nr   r9   r]  z?Qwen OAuth access token missing. Re-run 'qwen auth qwen-oauth'.rU   qwen_access_token_missingr:  HERMES_QWEN_BASE_URLrC  r  )rM  r$  rQ   r  expires_at_msr  )r@  r5   r6  r   r   rJ  rd  rK  r  r  rE  DEFAULT_QWEN_BASE_URLr7  )rh  ri  rj  rA  r   should_refreshr$  s          rL    resolve_qwen_runtime_credentialsrq  	  sP    #$$Fvzz."55;<<BBDDL-((N i1 i7

=8Q8QSghh I)&116::nb99?R@@FFHH 
M!,
 
 
 	
 y/44::<<CCCHHaLaH M22,..//  rK   c                 J   t                      } 	 t          d          }dt          |           |                    d          |                    d          |                    d          dS # t          $ r*}dt          |           t          |          dcY d }~S d }~ww xY w)	NT)ri  r  rQ   rn  )r  r  r  rQ   rn  F)r  r  rZ  )r7  rq  r5   r6  rK  )r>  re  r  s      rL   get_qwen_auth_statusrs  (	  s    #%%I
 1TJJJYii))yy++"YY77
 
 	
  
 
 
YXX
 
 	
 	
 	
 	
 	
 	

s   AA. .
B"8BB"B"	List[str]c                    | pt                                           }d |                                D             }t                      }g }|D ]0}||vr*|                    |           |                    |           1|S )Nc                    g | ]}||S rA   rA   )r   r  s     rL   rG  z'_spotify_scope_list.<locals>.<listcomp>E	  s    :::tT:d:::rK   )DEFAULT_SPOTIFY_SCOPEr   r  r  r  rI  )r  
scope_textr  seenorderedr=   s         rL   _spotify_scope_listr{  C	  s    44;;==J::z//11:::FUUDG " "HHUOOONN5!!!NrK   c                F    d                     t          |                     S )Nr.   )ry  r{  )r  s    rL   _spotify_scope_stringr}  O	  s    88'	22333rK   explicitc                   ddl m} |  |d           |d          t          |t                    r|                    d          nd f}|D ]+}t          |pd                                          }|r|c S ,t          ddd	
          )Nr   get_env_valueHERMES_SPOTIFY_CLIENT_IDSPOTIFY_CLIENT_IDr<   r9   zPSpotify client_id is required. Set HERMES_SPOTIFY_CLIENT_ID or pass --client-id.r/   spotify_client_id_missingr:  )r   r  r   rJ   r6  r5   r   rK  r~  r>  r  
candidates	candidater   s         rL   _spotify_client_idr  S	  s     0///// 	011)**",UD"9"9C		+t	J    	io2&&,,.. 	NNN	
Z(   rK   c                   ddl m} |  |d           |d          t          |t                    r|                    d          nd t
          f}|D ]+}t          |pd                                          }|r|c S ,t
          S )Nr   r  HERMES_SPOTIFY_REDIRECT_URISPOTIFY_REDIRECT_URIredirect_urir9   )r   r  r   rJ   r6  DEFAULT_SPOTIFY_REDIRECT_URIr5   r   r  s         rL   _spotify_redirect_urir  j	  s     0///// 	344,--%/t%<%<F		.!!!$$J    	io2&&,,.. 	NNN	''rK   c                   ddl m}  |d          t          | t                    r|                     d          nd t
          f}|D ]>}t          |pd                                                              d          }|r|c S ?t
          S )Nr   r  HERMES_SPOTIFY_API_BASE_URLapi_base_urlr9   rC  )	r   r  r   rJ   r6  DEFAULT_SPOTIFY_API_BASE_URLr5   r   rE  r>  r  r  r  r   s        rL   _spotify_api_base_urlr  ~	  s    ////// 	344%/t%<%<F		.!!!$$J
    	io2&&,,..55c:: 	NNN	''rK   c                   ddl m}  |d          t          | t                    r|                     d          nd t
          f}|D ]>}t          |pd                                                              d          }|r|c S ?t
          S )Nr   r   HERMES_SPOTIFY_ACCOUNTS_BASE_URLaccounts_base_urlr9   rC  )	r   r  r   rJ   r6  !DEFAULT_SPOTIFY_ACCOUNTS_BASE_URLr5   r   rE  r  s        rL   _spotify_accounts_base_urlr  	  s    ////// 	899*4UD*A*AK		%&&&t)J
    	io2&&,,..55c:: 	NNN	,,rK   @   lengthc                    t          j        t          j        |                                         d          }|                    d          d d         S Nasciir     r  urlsafe_b64encoder  urandomr  rE  r  rb  s     rL   _spotify_code_verifierr  	  C    

"2:f#5#5
6
6
=
=g
F
FC::c??4C4  rK   code_verifierc                    t          j        |                     d                                                    }t	          j        |                              d                              d          S Nry  r  r  r7  r8  r9  digestr  r  r  rE  r  r  s     rL   _spotify_code_challenger  	  X    ^M0099::AACCF#F++227;;BB3GGGrK   c                    t          j        t          j        |                                         d          }|                    d          d d         S r  r  r  s     rL   _oauth_pkce_code_verifierr  	  r  rK   c                    t          j        |                     d                                                    }t	          j        |                              d                              d          S r  r  r  s     rL   _oauth_pkce_code_challenger  	  r  rK   r<   r  code_challenger  c           
     >    t          | d|||d|d          }| d| S )NrN  S256)r<   response_typer  r=   r>  code_challenge_methodr  z/authorize?)r   )r<   r  r=   r>  r  r  querys          rL   _spotify_build_authorize_urlr  	  sH     $!'(   E  33E333rK   tuple[str, int, str]c                    t          |           }|j        dk    rt          ddd          |j        pd}|dvrt          ddd          |j        st          d	dd          ||j        |j        pd
fS )NhttpzHSpotify PKCE redirect_uri must use http://localhost or http://127.0.0.1.r/   spotify_redirect_invalidr:  r9   >   r  r  z?Spotify PKCE redirect_uri must point to localhost or 127.0.0.1.zBSpotify PKCE redirect_uri must include an explicit localhost port.rC  )r   r  rK  r  portr  )r  r  hosts      rL   _spotify_validate_redirect_urir  	  s    l##F}V+
 
 
 	

 ? bD---M+
 
 
 	

 ; 
P+
 
 
 	

 fk0S00rK   expected_path3tuple[type[BaseHTTPRequestHandler], dict[str, Any]]c                H     d d d d d G  fddt                     }|fS )N)rN  r>  rZ  error_descriptionc                  &    e Zd Zd
 fdZddZd	S )?_make_spotify_callback_handler.<locals>._SpotifyCallbackHandlerr   rR  c                   t          | j                  }|j        k    rE|                     d           |                                  | j                            d           d S t          |j                  }|                    dd g          d         d<   |                    dd g          d         d<   |                    dd g          d         d<   |                    dd g          d         d<   |                     d           | 	                    d	d
           |                                  d         rd}nd}| j                            |
                    d                     d S )N  s
   Not found.rN  r   r>  rZ  r  r#  r  ztext/html; charset=utf-8zW<html><body><h1>Spotify authorization failed.</h1>You can close this tab.</body></html>zY<html><body><h1>Spotify authorization received.</h1>You can close this tab.</body></html>ry  )r   r  send_responseend_headerswfiler  r   r  r6  send_headerr9  )rW  r  paramsra  r  results       rL   do_GETzF_make_spotify_callback_handler.<locals>._SpotifyCallbackHandler.do_GET	  si   di((F{m++""3'''  """
  ///fl++F#ZZ77:F6N$jj4&99!<F7O$jj4&99!<F7O*0**5H4&*Q*QRS*TF&'s###^-GHHHg sprJT[[1122222rK   formatr5   argsr   c                    d S rT  rA   )rW  r  r  s      rL   log_messagezK_make_spotify_callback_handler.<locals>._SpotifyCallbackHandler.log_message	  s    FrK   Nr   rR  )r  r5   r  r   r   rR  )rE   rF   rG   r  r  )r  r  s   rL   _SpotifyCallbackHandlerr  	  sL        	3 	3 	3 	3 	3 	3 	3.	 	 	 	 	 	rK   r  )r	   )r  r  r  s   ` @rL   _make_spotify_callback_handlerr  	  sc    !	 F       "8   6 #F**rK        f@r  dict[str, Any]c          	        t          |           \  }}}t          |          \  }} G d dt                    }	  |||f|          }n.# t          $ r!}	t	          d| d| d|	 dd          |	d }	~	ww xY wt          j        |j        d	d
id          }
|
                                 t          j
                    t          d|          z   }	 t          j
                    |k     r{|d         s|d         r@||                                 |                                 |
                    d           S t          j        d
           t          j
                    |k     {|                                 |                                 |
                    d           nC# |                                 |                                 |
                    d           w xY wt	          ddd          )Nc                      e Zd ZdZdS )4_spotify_wait_for_callback.<locals>._ReuseHTTPServerTN)rE   rF   rG   allow_reuse_addressrA   rK   rL   _ReuseHTTPServerr  
  s        "rK   r  z*Could not bind Spotify callback server on :r;  r/   spotify_callback_bind_failedr:  poll_intervalg?T)rl  kwargsdaemon      @rN  rZ  r  r  z?Spotify authorization timed out waiting for the local callback.spotify_callback_timeout)r  r  r
   r  rK  	threadingThreadserve_foreverstartr  r  r  shutdownserver_closery  r  )r  r  r  r  r  handler_clsr  r  serverr  threadr  s               rL   _spotify_wait_for_callbackr  
  sD   
 6lCCD$8>>K# # # # #: # # #!!4,<<   MMMMMMM/
 
 
 		 V%9?TWBXaefffF
LLNNN~#c?";";;H!n))f~   	C    	 JsOOO n))
 	C     	C    
I'   s*   A	 	
A4A//A4(F 5+F A G)previous_statetoken_payloadrequested_scoper  r  c               t   t          j        t          j                  }t	          |                     dd                    }t          j        |                                |z   t          j                  }	t          |pi           }
|
	                    |||||t          |                     d          p|                                          t          |                     dd          pd                                          pdt          |                     dd          pd                                          t          |                     d	          p|
                    d	          pd                                          |                                |	                                |d
d           |
S )Nr  r   r  r=   rZ  r[  r   r9   rM  
oauth_pkce)r<   r  r  r  r=   granted_scoperZ  r   rM  r!  r  r  r8   )r   r  r   r  r  r6  r  r  rJ   r  r5   r   r  )r  r<   r  r  r  r  r  r  r  r  r>  s              rL   _spotify_token_payload_to_stater  *
  s    ,x|
$
$C$]%6%6|Q%G%GHHJ'*(DVVVJ%2&&E	LL$.$ ]..w77J?KKQQSS-++L(CCOxPPVVXXd\dM--nbAAGRHHNNPPo.. yy))
 
 %''}} **,, !#    & LrK   rN  c           
        	 t          j        | dddi| d|||d|          }n(# t          $ r}t          d| dd	
          |d }~ww xY w|j        dk    r5|j                                        }t          d|rd| ndz   dd	
          |                                }	t          |	t                    r7t          |	                    dd          pd                                          st          ddd
          |	S )N
/api/tokenr  rO  authorization_code)r<   rS  rN  r  r  rT  zSpotify token exchange failed: r/   spotify_token_exchange_failedr:  rV  zSpotify token exchange failed.rW  r9   r   z7Spotify token response did not include an access_token.spotify_token_exchange_invalid)r'  r(  r   rK  r)  r  r   r"  r   rJ   r5   r6  )
r<   rN  r  r  r  r  r`  r  detailr  s
             rL   !_spotify_exchange_code_for_tokensr  N
  su   : ,,,#%HI&2 ,!.  $
 
 
    3c330
 
 
 		 s""$$&&,)/7%V%%%R90	
 
 
 	
 mmooGgt$$ 
CNB0O0O0USU,V,V,\,\,^,^ 
E1
 
 
 	

 Ns   #& 
AAAc          
        t          |                     dd          pd                                          }|st          dddd          t	          |           }t          |           }	 t          j        | d	d
did||d|          }n(# t          $ r}t          d| dd          |d }~ww xY w|j	        dk    r6|j
                                        }t          d|rd| ndz   ddd          |                                }t          |t                    r7t          |                    dd          pd                                          st          dddd          t          ||t          |           t          |                     d          pt                     |t#          |           |           S )NrM  r9   z?Spotify refresh token missing. Run `hermes auth spotify` again.r/   spotify_refresh_token_missingTrL  r2  r  r  rO  rR  rT  zSpotify token refresh failed: spotify_refresh_failedr:  rV  z>Spotify token refresh failed. Run `hermes auth spotify` again.rW  r   z9Spotify refresh response did not include an access_token.spotify_refresh_invalidr=   )r<   r  r  r  r  r  )r5   r6  r   rK  r  r  r'  r(  r   r)  r  r"  r   rJ   r  r  rw  r  )	r>  r  rM  r<   r  r`  r  r  r  s	            rL   _refresh_spotify_oauth_stater  }
  sB   
 		/266<"==CCEEM 
M0!	
 
 
 	
 #///I2599: ,,,#%HI-!.& 
 $	
 	
 	
    2S22)
 
 
 		 s""$$&&L)/7%V%%%R9)!
 
 
 	
 mmooGgt$$ 
CNB0O0O0USU,V,V,\,\,^,^ 
G*!	
 
 
 	
 +*777EIIg..G2GHH+*511   s   -!B 
B4B//B4c                   t                      5  t                      }t          |d          }|st          dddd          t	          |           }|s%|r#t          |                    d          |          }|r!	 t          |          }t          |d|d           t          |           n# t          $ r}|j
        r|                    d	          rd
D ]}|                    |d            d|j        pdt          |          ddt          j        t           j                                                  d|d<   	 t          |d|d           t          |           n2# t&          $ r%}t(                              d|           Y d }~nd }~ww xY w d }~ww xY wd d d            n# 1 swxY w Y   t          |                    dd          pd                                          }	|	st          dddd          d|	|	t          |                    dd          pd          t/          |          t          |                    d          p|                    d          pd                                          t1          |          t3          |          |                    d          t          |                    d	d          pd                                          d
S )Nr/   z>Spotify is not authenticated. Run `hermes auth spotify` first.spotify_auth_missingTrL  r  Fr$  rM  r   rM  r  r  r!  refresh_failedruntime_refresh_failurerM  rN  rP  r  rO  atlast_auth_errorz6Spotify OAuth: failed to persist quarantined state: %sr   r9   z>Spotify access token missing. Run `hermes auth spotify` again.spotify_access_token_missingrZ  r[  r  r=   r2  )
rM  r   rQ   rZ  r$  r=   r<   r  r  rM  )r  r4  r5  rK  r   r  r6  r  r'  r  rO  r[  rN  r5   r   r  r   r  r  r   r   r*  r   r  r  r  )
rh  ri  rj  r=  r>  rp  r  _k	_save_excr   s
             rL   #resolve_spotify_runtime_credentialsr  
  s    
		 ' '%''
$Z;; 	P"+!%	    m,, 	Y"5 	Y)%))L*A*ACWXXN 	4U;;%j)UuUUUU ,,,,   ' jEIIo,F,F j k , ,		"d++++$- # <,<#&s88";,0&l8<88BBDD0 0E+,j-j)UW\]]]](4444$ j j j%]_hiiiiiiiij)'' ' ' ' ' ' ' ' ' ' ' ' ' ' 'R uyy44:;;AACCL 
L/!	
 
 
 	
 $%))L(;;GxHH)%00UYY//K599W3E3EKLLRRTT'e444-E:::ii--UYY;;ArBBHHJJ  sa   A-F)=1B/.F)/
F9BF?"E"!F"
F,FFFFFF))F-0F-c            
        t          d          } | sddiS |                     d          }t          |                     dd          pd                                          }t	          |pt          |d                     |                     dd	          |                     d
          |                     d          |                     d          p|                     d          ||                     d          t	          |          dS )Nr/   r  Fr  rM  r9   r   r8   r  r<   r  r  r=   r  )r  r8   r<   r  r=   r  r  has_refresh_token)r^  r6  r5   r   r   r  )r>  r  rM  s      rL   get_spotify_auth_statusr    s    #I..E $U##<((J		/266<"==CCEEM-J|J/J/J+JKKYY{L99YY{++		.11?++Auyy/A/A 		.11!-00	 	 	rK   redirect_uri_hintc                   ddl m} t                       t          d           t          d           t          d           t                       t          d           t          d           t          d           t                       t          dt                      t                       t          d	           t          d
t           d           t          d           t          d           t          d           t          d|             t          d           t          d           t          d           t          d           t                       t                      s+	 t          j        t                     n# t          $ r Y nw xY w	 t          d          
                                }n2# t          t          f$ r t                       t          d          w xY w|s5t                       t          dt           d           t          d           |d|           | r| t          k    r |d|            t                       t          d           t                       |S )zWalk the user through creating a Spotify developer app, persist the
    resulting client_id to ~/.hermes/.env, and return it.

    Raises SystemExit if the user aborts or submits an empty value.
    r   )save_env_valuezF======================================================================zSpotify first-time setupz=Spotify requires every user to register their own lightweightz>developer app. This takes about two minutes and only has to bezdone once per machine.zFull guide: zSteps:z  1. Opening z in your browser...z$  2. Click 'Create app' and fill in:z1       App name:     anything (e.g. hermes-agent)z       Description:  anythingz       Redirect URI: z       API/SDK:      Web APIz$  3. Agree to the terms, click Save.z9  4. Open the app's Settings page and copy the Client ID.z  5. Paste it below.zSpotify Client ID: zSpotify setup cancelled.zNo Client ID entered. See z for the full guide.z)Spotify setup cancelled: empty Client ID.r  r  z0Saved HERMES_SPOTIFY_CLIENT_ID to ~/.hermes/.env)r   r  printSPOTIFY_DOCS_URLSPOTIFY_DASHBOARD_URL_is_remote_session
webbrowserr  r   inputr   EOFErrorKeyboardInterrupt
SystemExitr  )r  r  rb  s      rL   _spotify_interactive_setupr    s    100000	GGG	(OOO	
$%%%	(OOO	GGG	
IJJJ	
JKKK	
"###	GGG	
+)
+
+,,,	GGG	(OOO	
D/
D
D
DEEE	
0111	
=>>>	
)***	
5"3
5
5666	
()))	
0111	
EFFF	
 !!!	GGG 	O12222 	 	 	D	5)**0022'( 5 5 534445  FQ+;QQQRRRDEEE N-s333  I.2NNN46GHHH	GGG	
<===	GGGJs   /F	 	
FF!F< </G+c                ,   t          d          pi }t          | dd           }	 t          ||          }nS# t          $ rF}t          |dd          dk    r t	          t          | dd           pt
                    }Y d }~nd }~ww xY wt          t          | dd           |          }t          t          | dd           p|                    d                    }t          |          }t          |          }t          | d	d
           }	t                      }
t          |
          }t          j                    j        }t!          ||||||          }t#          d           t#          d|            t#          d|            t#          d           t#                       t#          d           t#          |           t#                       t#          dt$                      t#                       t'          |t$                     |	ret)                      sWt+                      rI	 t-          j        |          }n# t0          $ r d
}Y nw xY w|rt#          d           nt#          d           t3          |t5          t          | dd           pd                    }|                    d          r/|                    d          p|d         }t7          d|           |                    d          |k    rt7          d          t9          |t;          |                    d          pd          ||
|t5          t          | dd           pd                    }t=          ||||||          }t?                      5  tA                      }tC          |d|d
            tE          |          }d d d            n# 1 swxY w Y   t#          d!           t#          d"|            t#          d#           t#          d$t$                      d S )%Nr/   r<   rN  r9   r  r  )r  r=   
no_browserF)r<   r  r=   r>  r  r  zStarting Spotify PKCE login...zClient ID: zRedirect URI: zIMake sure this redirect URI is allow-listed in your Spotify app settings.z"Open this URL to authorize Hermes:zFull setup guide: docs_urlz)Browser opened for Spotify authorization.z<Could not open the browser automatically; use the URL above.r  r  r  rZ  r  zSpotify authorization failed: r>  z-Spotify authorization failed: state mismatch.rK  )r<   rN  r  r  r  r  )r<   r  r  r  r  r$  zSpotify login successful!  Auth state: z.  Provider state saved under providers.spotifyz  Docs: )#r^  r  r  rK  r  r  r  r}  r6  r  r  r  r  r  r  r  r  r  r  _print_loopback_ssh_hintr  _can_open_graphical_browserr  r  r   r  r  r  r  r5   r  r  r4  r'  r  )r  existing_stateexplicit_client_idr<   r  r  r=   r  r  open_browserr  r  state_nonceauthorize_urlopenedcallbackr  r  spotify_stater=  saved_tos                        rL   login_spotify_commandr.  S  s   ,Y77=2N
 !{D99
&'9>JJ		 
 
 
3##'BBB.%dNDAAaEa
 
 
						
 )~t)L)Ln]]L!'$">">"].BTBTU\B]B]^^E2>BB(88Lt\5999L*,,M,];;N*,,"K0!%+  M 

*+++	
#	
#
#$$$	
)<
)
)***	
UVVV	GGG	
.///	-	GGG	
1/
1
1222	GGG\4DEEEE R.00 R5P5R5R R	_]33FF 	 	 	FFF	 	R=>>>>PQQQ)gdIt<<EFF  H ||G D122Ghw6GB&BBCCC||G++HIII5f%%+,,!#+gdIt<<DEE  M 4!+!  M 
		 0 0%''
j)]uUUUU#J//0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 

%&&&	
%8
%
%&&&	
:;;;	
'%
'
'(((((s8   5 
B<B  B*H? ?II1OO	Oc                     t          j        d          st          j        d          rdS dD ]} t          j        |           r dS dS )uH  Detect environments where loopback OAuth can't reach the local browser.

    Historically only SSH was checked, but #26923 surfaced that
    **browser-only remote consoles** (GCP Cloud Shell, GitHub
    Codespaces, AWS EC2 Instance Connect, Gitpod, Replit, etc.) hit
    the exact same problem — the user has a browser on their laptop
    but the loopback listener is bound on the remote VM that the
    laptop's browser can't reach.  These environments typically don't
    set ``SSH_CLIENT`` / ``SSH_TTY``, so the SSH-only check left
    them with no guidance and no fallback.
    
SSH_CLIENTSSH_TTYT)CLOUD_SHELL
CODESPACESCODESPACE_NAMEGITPOD_WORKSPACE_IDREPL_ID
STACKBLITZF)r  r  )r   s    rL   r  r    s`     
y ")I"6"6 t 	 	 9S>> 	44	5rK   >   www-browserw3mlynxlinksbrowshelinkslinks2_CONSOLE_BROWSER_NAMESc                    ddl } dd}t          j                            dd	          }|r ||          rd
S t          j                            d          rQt          t          j                            d          pt          j                            d                    }|s|sd
S 	 |                                 }n# t          $ r Y d
S w xY wt          |dd	          pt          |dd	          pd	}|r ||          rd
S dS )u  Return True only when a *graphical* browser is likely to open.

    ``webbrowser.open()`` resolves to whatever the platform offers, and on a
    headless / CLI-only Linux box with no GUI browser installed that is often
    a text-mode browser (w3m/lynx/links) which launches inside the terminal
    and takes over the user's session.  This guard distinguishes "a real
    windowed browser will pop up" from "a console browser will hijack the
    TTY", so callers can fall back to printing the URL instead.

    Heuristics:
      * Respect ``$BROWSER`` — if it names a known console browser, refuse.
      * On Linux, require a display server (``$DISPLAY`` / ``$WAYLAND_DISPLAY``)
        unless ``$BROWSER`` points at something graphical; no display server
        almost always means no GUI browser.
      * Ask ``webbrowser.get()`` what it resolved to and refuse when the
        underlying command is a known console browser.
      * macOS and Windows always have a usable default GUI browser.
    r   Nr   r5   r   r   c                    |                                  r,|                                                                  d         nd}t          j                            |                                          }|t          v S )Nr   r9   )r   r  r  r  basenamer   r?  )r   r  bases      rL   _names_console_browserz;_can_open_graphical_browser.<locals>._names_console_browser  s_    ,1KKMMA##%%a((rw&&,,..---rK   BROWSERr9   FlinuxDISPLAYWAYLAND_DISPLAYr7   rB  T)r   r5   r   r   )
r  r  r  r6  sysplatformr   r   r   r  )_webbrowserrD  browser_envhas_display
controllerr  s         rL   r$  r$    sN   & %$$$. . . .
 *..B//K --k:: u
|w'' JNN9%%J8I)J)J
 
  	; 	5 __&&

   uu
 	
FB'' 	:z2..	 
  ++I66 u4s   )B> >
CCc                     	 ddl } |                                 pd}n# t          $ r d}Y nw xY wt          j        d          pt          j        d          pd}| d| S )zReturn best-effort 'user@hostname' for the SSH tunnel hint command.

    Falls back to placeholder tokens when the values cannot be determined so
    the hint is always syntactically valid even if not copy-pasteable.
    r   Nz<this-host>USERLOGNAMEz<user>@)socketgethostnamer  r  r  )_socketr  r  s      rL   _ssh_user_at_hostrV     s    !    &&((9M ! ! ! !9V@	) 4 4@DXs    ,,r   r!  
str | Nonec          	        t                      sdS 	 t          |           }n# t          $ r Y dS w xY w|j        pd}|j        }|dvs|sdS d}t                       t          |           t          d           t          |           t          d|             t          d           t          d           t                       t          d	| d
| dt                                  t                       t          d           |rt          d|            t          dt                      t          |           t                       dS )ap  Print an SSH tunnel hint when running a loopback-redirect OAuth flow on a
    remote host. The auth server (Spotify, MCP servers, ...) will redirect the
    user's browser to ``127.0.0.1:<port>/callback``. If the browser is on a
    different machine than the loopback listener (the usual SSH case), the
    redirect can't reach the listener without a local port forward.

    The hint is best-effort: silent if we don't think we're remote, or if we
    can't parse a host/port out of the redirect URI.

    Pass ``docs_url`` for a provider-specific guide; the generic OAuth-over-SSH
    guide is always shown after it.
    Nr9   >   ::1r  r  z<------------------------------------------------------------u/   Remote session detected — SSH tunnel requiredz,Hermes is waiting for the OAuth callback on z<but your browser is on a different machine. Run this commandz?in a NEW terminal on your local machine BEFORE opening the URL:z  ssh -N -L z:127.0.0.1:r.   z8Then open the authorize URL above in your local browser.zProvider docs:      zSSH/jump-box guide: )r  r   r   r  r  r  rV  OAUTH_OVER_SSH_DOCS_URL)r  r!  r  r  r  dividers         rL   r#  r#  /  s|     ,''   ? bD;D444D4G	GGG	'NNN	
;<<<	'NNN	
G
G
GHHH	
HIII	
KLLL	GGG	
F
F
F$
F
F1B1D1D
F
FGGG	GGG	
DEEE 1/X//000	
:!8
:
:;;;	'NNN	GGGGGs   " 
00_lockr]  c                   | r5t                      5  t                      }ddd           n# 1 swxY w Y   nt                      }t          |d          }|st          dddd          |                    d          }t          |t                    st          ddd	d          |                    d
          }|                    d          }t          |t                    r|                                st          dddd          t          |t                    r|                                st          dddd          ||                    d          dS )zRead Codex OAuth tokens from Hermes auth store (~/.hermes/auth.json).
    
    Returns dict with 'tokens' (access_token, refresh_token) and 'last_refresh'.
    Raises AuthError if no Codex tokens are stored.
    NrO   ?No Codex credentials stored. Run `hermes auth` to authenticate.codex_auth_missingTrL  rA  zICodex auth state is missing tokens. Run `hermes auth` to re-authenticate.codex_auth_invalid_shaper   rM  zICodex auth is missing access_token. Run `hermes auth` to re-authenticate.codex_auth_missing_access_tokenJCodex auth is missing refresh_token. Run `hermes auth` to re-authenticate. codex_auth_missing_refresh_tokenlast_refreshrA  re  )	r  r4  r5  rK  r6  r   rJ   r5   r   )r]  r=  r>  rA  r   rM  s         rL   _read_codex_tokensrg  a  s     ( 	, 	,)++J	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, &''
 ^<<E 
M#%!	
 
 
 	
 YYx  Ffd## 
W#+!	
 
 
 	
 ::n--LJJ//MlC(( 
0B0B0D0D 
W#2!	
 
 
 	
 mS)) 
1D1D1F1F 
X#3!	
 
 
 	
 		.11     ,00re  previous_singleton_tokensc                   |                     d          }|sdS |                     d          }|                      d          }t          |t                    sdS |                     d          }t          |t                    sdS d}t          |t                    r|                     d          pd}|D ]}	t          |	t                    s|	                     d          }
|
dk    rd}n1|
d	k    r)t	          |o|	                     d          |k              }nd
}|sj||	d<   |r||	d<   |r||	d<   d|	d<   d|	d<   d|	d<   d|	d<   d|	d<   d|	d<   dS )u	  Mirror a fresh Codex re-auth into the credential_pool OAuth entries.

    The runtime selects credentials from ``credential_pool.openai-codex``, not
    from ``providers.openai-codex.tokens``.  A re-auth invalidates the prior
    OAuth pair server-side, but pool entries keep holding the now-consumed
    refresh token plus any stale error markers — so the next request spends a
    dead token and gets a 401 ``token_invalidated``.

    What gets refreshed:

    * ``device_code`` — the singleton-seeded entry written by the device-code
      OAuth flow when the user logged in via ``hermes setup`` / the model
      picker.  Always synced with the fresh tokens.
    * ``manual:device_code`` — entries created by ``hermes auth add openai-codex``
      that use the same device-code OAuth mechanism.  ONLY synced if the
      entry's existing access_token matches the *previous* singleton
      access_token (i.e. the entry is a legacy singleton-alias from the
      #33000 workaround era).  Manual entries whose tokens never matched the
      singleton represent INDEPENDENT accounts added via
      ``hermes auth add openai-codex`` and must not be overwritten by a
      re-auth that targeted a different account (regression for #39236).

      The original #33538 fix refreshed every ``manual:device_code`` entry
      unconditionally.  That worked when ``manual:device_code`` only meant
      "legacy alias of the singleton", but the same source string is now
      also produced by independent-account additions, and the broad sync
      silently clobbered distinct accounts with the latest-authenticated
      token pair.  The access_token-match check distinguishes the two cases
      without changing the source-string contract.

    What does NOT get refreshed:

    * ``manual:api_key`` and any other non-device-code manual sources — those
      are independent credentials (an explicit API key, a different ChatGPT
      account, etc.) and must not be overwritten by a single re-auth.
    * ``manual:device_code`` entries whose access_token does NOT match the
      previous singleton — see above; these are independent accounts.

    Error markers (``last_status``, ``last_error_*``) are cleared ONLY on
    entries that actually had their tokens rewritten by this re-auth.
    Independent entries keep their own error state (their 401/429 markers
    belong to that account's own auth flow, not this re-auth).
    r   NrM  r  rO   r  r&   Tzmanual:device_codeFre  last_statuslast_status_atlast_error_codelast_error_reasonlast_error_messagelast_error_reset_at)r6  r   rJ   r4  r   )r=  rA  re  ri  r   rM  r	  r?  prev_atr
  r  refresh_this_entrys               rL   _sync_codex_pool_entriesrs    s   b ::n--L JJ//M>>+,,DdD!! hh~&&Ggt$$  G+T22 H+//??G4 , ,%&& 	8$$]""!%+++ "&@EIIn55@" "
 "'! 	 ,n 	3%2E/" 	1$0E.!#m"&#' %)!"&*"#'+#$$?, ,rK   r%  c                   |Dt          j        t          j                                                                      dd          }t                      5  t                      }t          |d          pi }t          |
                    d          t                    r|
                    d          nd}| |d<   ||d<   d|d<   |rEt          |                                          r$t          |                                          |d	<   t          |d|           t          || ||
           t!          |           ddd           dS # 1 swxY w Y   dS )zCSave Codex OAuth tokens to Hermes auth store (~/.hermes/auth.json).Nr  r  rO   rA  re  chatgpt	auth_moder%  )ri  )r   r  r   r  r  r  r  r4  r5  r   r6  rJ   r5   r   r;  rs  r  )rA  re  r%  r=  r>  ri  s         rL   _save_codex_tokensrw    s   |HL11;;==EEhPSTT			 % %%''
$Z@@FB <FeiiPXFYFY[_;`;`$jEIIh$7$7$7fj! h ,n&k 	0SZZ%%'' 	0 ZZ--//E'NZ??? &?		
 	
 	
 	
 	$$$+% % % % % % % % % % % % % % % % % %s   C)EEEr  c                t   t                      }|rnt          |                    dd          pd                                          r7t          |                    dd          pd                                          sdS t                              d|            t          |           t          |          S )zBAdopt a valid Codex CLI token pair into Hermes auth, if available.r   r9   rM  Nz3Codex auth recovered from Codex CLI auth.json (%s).)_import_codex_cli_tokensr5   r6  r   r   r<  rw  rJ   )r  importeds     rL   _recover_codex_tokens_from_clir{    s    '))H 	^R006B77==?? _b117R88>>@@
 t
KKEvNNNx   >>rK   rM  c          	        ~ t          |t                    r|                                st          dddd          t	          j        t          dt          |                              }t	          j        |dt          d	          5 }|
                    t          d
did|t          d          }ddd           n# 1 swxY w Y   |j        dk    rAt          t          |dd                    }|d| d}nd}t          |dt           d          |j        dk    r	d}d|j         d}d}		 |                                }
t          |
t$                    r|
                    d          }t          |t$                    r|                    d          p|                    d          }t          |t                    r(|                                r|                                }|                    d          }t          |t                    r+|                                rd|                                 }nt          |t                    r|                                r~|                                }|
                    d          p|
                    d          }t          |t                    r+|                                rd|                                 }n# t(          $ r Y nw xY w|d v rd}	|d!k    rd"}d}	|j        d#v r|	sd}	t          |d||	          	 |                                }n&# t(          $ r}t          d$dd%d          |d}~ww xY w|                    d&          }t          |t                    r|                                st          d'dd(d          |                                |                                t+          j        t.          j                                                                      d)d*          d+}|                    d          }t          |t                    r+|                                r|                                |d<   |S ),z>Refresh Codex OAuth tokens without mutating Hermes auth state.rc  rO   rd  TrL  r  r  )rQ  z
User-Agentr  r!  r  rO  rM  rR  r!  r?  N  r!  2Codex provider quota exhausted (429); retry after s. Credentials are still valid.fCodex provider quota exhausted (429). Credentials are still valid; retry after the usage limit resets.Fr#  codex_refresh_failedz'Codex token refresh failed with status r  rZ  rN  typerP  zCodex token refresh failed: r  >   invalid_grantinvalid_tokeninvalid_requestrefresh_token_reusedzCodex refresh token was already consumed by another client (e.g. Codex CLI or VS Code extension). Run `codex` in your terminal to generate fresh tokens, then run `hermes auth` to re-authenticate.>       z*Codex token refresh returned invalid JSON.codex_refresh_invalid_jsonr   z6Codex token refresh response was missing access_token."codex_refresh_missing_access_tokenr  r  )r   rM  re  )r   r5   r   rK  r'  Timeoutr  r  ClientCODEX_OAUTH_USER_AGENTr(  CODEX_OAUTH_TOKEN_URLCODEX_OAUTH_CLIENT_IDr)  rd  r  r\  r"  rJ   r6  r   r   r  r   r  r  r  )r   rM  r  r  clientr`  retry_afterrP  rN  rO  errerr_objnested_code
nested_msgerr_descrefresh_payloadr  refreshed_accessupdatednext_refreshs                       rL   refresh_codex_oauth_purer  #  sM    	mS)) 
1D1D1F1F 
X#3!	
 
 
 	
 mCU?%;%;<<==G	(0
 

 
 
 
 
;;!#%HI-!.2   
 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
" s"" 19d1S1STT"/[ / / / G6  #("	
 
 
 	
 s""%SH<PSSS 	--//C#t$$ T'''**gt,, T")++f"5"5"LV9L9LK!+s33 38I8I8K8K 3*0022!(Y!7!7J!*c22 Vz7G7G7I7I V"UAQAQASAS"U"U-- T'--// T"==??D"ww':;;Qswwy?Q?QH!(C00 TX^^5E5E T"SAQAQ"S"S 	 	 	D	HHH#)))=   $ :--6F-##-	
 
 
 	
"--//   8#-!	
 
 

 	 '**>::&,, 
4D4J4J4L4L 
D#5!	
 
 
 	
 )..00&,,.. X\22<<>>FFxQTUU G
 #&&77L,$$ 8););)=)= 8#/#5#5#7#7 Ns=   )C  CC3F8K, ,
K98K9-M 
M%M  M%c           	        	 t          t          |                     dd          pd          t          |                     dd          pd          |          }nQ# t          $ rD}t	          |dd          s t          dt	          |dd	          pd
           }|s |cY d	}~S d	}~ww xY wt          |           }|d         |d<   |d         |d<   t          |           |S )zzRefresh Codex access token using the refresh token.
    
    Saves the new tokens to Hermes auth store automatically.
    r   r9   rM  r  rO  Fzrefresh_token rejected: rN  N
auth_error)r  r5   r6  rK  r  r{  rJ   rw  )rA  r  rc  r  rz  updated_tokenss         rL   _refresh_codex_auth_tokensr    s   ,

>2..4"55

?B//5266+
 
 
		
     s.66 	1SwsFD'A'A'Q\SS
 
  	), &\\N%.~%>N>"&/&@N?#~&&&s   AA 
B)%9B$B)$B)c                    t          j        dd                                          } | s#t          t	          j                    dz            } t	          |                                           dz  }|                                sdS 	 t          j	        |
                                          }|                    d          }t          |t                    sdS |                    d          }|                    d          }|r|sdS t          |d	          rt                              d
|           dS t          |          S # t"          $ r Y dS w xY w)zTry to read tokens from ~/.codex/auth.json (Codex CLI shared file).
    
    Returns tokens dict if valid and not expired, None otherwise.
    Does NOT write to the shared file.
    
CODEX_HOMEr9   z.codexr  NrA  r   rM  r   u7   Codex CLI tokens at %s are expired — skipping import.)r  r  r   r5   r   r  
expanduseris_filer"  r  r  r6  r   rJ   r5  r   r*  r   )
codex_homer>  r  rA  r   rM  s         rL   ry  ry    sZ    <,,2244J 1x/00
Z  ++--;I t*Y002233X&&&$'' 	4zz.11

?33 	= 	4 +<;; 	LLI9   4F||   tts%   AE .E 
+E 7E 
EEc           
     >   d}	 t                      }n# t          $ r}}|}t          |dd          r^t          |dd          dv rKt          t	          t          |dd          pd                    }|r||                    d          d}nd}nd}Y d}~nd}~ww xY w|t                      }|rJt          j        d	d
          	                                
                    d          pt          }d||ddddS t                      }	|	r|	                    d          }
t          |
t          t          f          rA|
t!          j                    k    r*t          |
t!          j                    z
            }d| d}nd}t          |dt"          d          ||t          dddd          t%          |d                   }t	          |                    dd
          pd
          	                                }t'          dd          }t)          |           }|s|rt+          ||          }|rt-          t/          t          t0                    |dz                       5  t          d          }t%          |d                   }t	          |                    dd
          pd
          	                                }t)          |           }|s|rt+          ||          }|rGt3          ||          }t	          |                    dd
          pd
          	                                }ddd           n# 1 swxY w Y   t          j        d	d
          	                                
                    d          pt          }d||d|                    d          ddS ) u  Resolve runtime credentials from Hermes's own Codex token store.

    Falls back to the credential pool when the singleton (``providers.openai-codex.tokens``)
    has no usable access_token but the pool (``credential_pool.openai-codex``) does. This
    closes the divergence between the chat path (singleton-only via this function) and
    the auxiliary path (pool-first via ``_read_codex_access_token``). Without this
    fallback, a user whose tokens live only in the pool — for example after a manual
    pool seed, a partial re-auth, or pool-only restoration from a backup — gets a bare
    HTTP 401 ``Missing Authentication header`` from the wire instead of a usable
    credential. See issue #32992.
    NrO  FrN  >   ra  rb  rd  r  re  rf  HERMES_CODEX_BASE_URLr9   rC  rO   r  ru  rM  r$  rQ   r  re  rv  reset_atr  r  r  rL  r_  r`  TrA  r   $HERMES_CODEX_REFRESH_TIMEOUT_SECONDS   r  r  r\  hermes-auth-store)rg  rK  r  r{  r5   r6  _pool_codex_access_tokenr  r  r   rE  DEFAULT_CODEX_BASE_URL_codex_pool_rate_limit_statusr   r   r  r  r\  rJ   r!   r   r5  r  r  AUTH_LOCK_TIMEOUT_SECONDSr  )rh  ri  rj  
read_errorr?  r  rz  
pool_tokenr$  pool_rate_limitr  	remainingrP  rA  r   refresh_timeout_secondsrp  s                    rL   !resolve_codex_runtime_credentialsr    si   " '+J!##   
3*E22 	wsFD7Q7Q V
 8
 8

 6c'#vt:T:T:dXd6e6effH "*HLL<X<XYYD |-//
 		1266<<>>EEcJJ *) 
 +$%+ $&   899 	&**:66H(S%L11 
h6L6L49;; 677	3 3 3 3 :  ',!&	    !M#%!	
 
 
 	
 $x.!!Fvzz."55;<<BBDDL'(NPRSS-((N ] 3 ]8G[\\ Qc%8Q2R2RTknqTq.r.rsss 	Q 	Q%E222D$x.))Fvzz."==CDDJJLLL!-00N" e(; e!@Oc!d!d Q3F<STT"6::nb#A#A#GRHHNNPP	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 		)2..4466==cBB 	"!  #%00  s(    
BA3BB"C	L77L;>L;c                    dd} 	 t                      5  t                      }ddd           n# 1 swxY w Y   |                    d          }t          |t                    sdS |                    d          }t          |t
                    sdS t          j                    }|D ]}t          |t                    s|                    d	          }t          |t                    r|                                sX|                    d
          dk    rr|                    d          }t          |                    d          pd          	                                }t          |                    d          pd          	                                }	|dk    pd|v pd|v pd|v pd|	v pd|	v pd|	v }
|
s | |                    d                    }|||k    r=|                    d          |                    d          ||                    d          |                    d          dc S n,# t          $ r t                              dd           Y nw xY wdS )zCReturn metadata for a pool-only Codex credential in quota cooldown.r   r   r   r  c                   | | dk    rd S t          | t          t          f          r$t          |           }|dk    rd S |dk    r|dz  n|S t          | t                    r|                                 }|sd S 	 t          |          }n# t
          $ r d }Y nw xY w||dk    r|dz  n|S 	 t          j        |                    dd                    	                                S # t
          $ r Y d S w xY wd S )Nr9   r   l    J)     @@r  r  )
r   r   r  r5   r   r   r   r  r  r  )r   numericrb  s      rL   _parse_reset_atz6_codex_pool_rate_limit_status.<locals>._parse_reset_atm  s9   =ERKK4ec5\** 	PEllG!||t'.1B'B'B7V##OeS!! 	++--C t**   "+25F+F+Fw''GS-ckk#x.H.HIISSUUU   ttts$   9B	 	BB+9C% %
C32C3Nr  rO   r   rk  	exhaustedrm  rn  r9   ro  r  
rate_limitusage_limitquotaz
rate limitzusage limitrp  r%  re  )r%  re  r  r  rP  z#Codex pool rate-limit lookup failedTexc_infor   r   r   r  )r  r4  r6  r   rJ   r4  r  r5   r   r   r   r   r*  )r  r=  r	  r?  r  r
  r  rN  r  rP  is_rate_limitedr  s               rL   r  r  k  s      0+K 	, 	,)++J	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	,~~/00$%% 	4((>**'4(( 	4ikk 	 	EeT** IIn--EeS))  yy'';6699.//D#677=2>>DDFFF%))$899?R@@FFHHG &6)& F*& f$&  7*	&
 !G+& g%  # &uyy1F'G'GHHH#C7++ %		. 9 9$))$788 99%9::    3	@  K K K:TJJJJJK4s9   H; /H; 3H; 3-H; &*H; F'H; ;&I$#I$c                    	 t                      5  t                      } ddd           n# 1 swxY w Y   |                     d          }t          |t                    sdS |                    d          }t          |t
                    sdS dd	}|D ]D} ||          r7t          |                    d
d                                                    c S En,# t          $ r t          
                    dd           Y nw xY wdS )a  Return the most-recent usable access_token from the openai-codex pool.

    Used as a fallback by ``resolve_codex_runtime_credentials`` when the
    singleton has no creds.  Reads ``credential_pool.openai-codex`` entries
    directly from auth.json and picks the first non-empty access_token,
    preferring entries that are not currently in an exhaustion cooldown.
    Returns ``""`` when no usable entry is found (caller handles by raising
    the original AuthError).
    Nr  r9   rO   r
  r?   r   r   c                H   t          | t                    sdS |                     d          }t          |t                    r|                                sdS |                     d          }t          |t
          t          f          r|t          j                    k    rdS dS )NFr   rp  T)r   rJ   r6  r5   r   r   r  r  )r
  r  r  s      rL   _entry_usablez/_pool_codex_access_token.<locals>._entry_usable  s    eT** uIIn--EeS))  uyy!677H(S%L11 h6L6Lu4rK   r   z!Codex pool fallback lookup failedTr  )r
  r?   r   r   )r  r4  r6  r   rJ   r4  r5   r   r   r   r*  )r=  r	  r?  r  r
  s        rL   r  r    s|   I 	, 	,)++J	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	,~~/00$%% 	2((>**'4(( 	2
	 
	 
	 
	  	B 	BE}U## B599^R8899??AAAAAB	B  I I I84HHHHHI2s?   C +C /C /-C "*C A	C C &DDc                   t          | d          }t          |t                    r|                    d          nd}t          |t                    rtt	          |                    dd          pd                                          }t	          |                    dd          pd                                          }|r|r|S |                     d          }t          |t                    r|                    d          nd}t          |t                    r|D ]}t          |t                    st	          |                    dd          pd                                          }t	          |                    dd          pd                                          }|r|st          |pi           }||t	          |                    d          pd	          d
|d<   |                    d          r|                    d          |d<   |                    dd           |c S t          |t                    r|ndS )zEReturn usable xAI OAuth state from provider state or credential pool.rT   rA  Nr   r9   rM  r  rZ  r[  )r   rM  rZ  re  rv  r  )r5  r   rJ   r6  r5   r   r4  r  )	r=  r>  rA  r   rM  r  r?  r
  r7  s	            rL   _xai_oauth_state_from_storer    s@    [99E$.ud$;$;EUYYx   F&$ 6::nb99?R@@FFHHFJJ;;ArBBHHJJ 	M 	L nn%677O ot,,	K((( 
 '4    	 	EeT** uyy<<BCCIIKKL		/2 > > D"EEKKMMM } %+2&&F ,!.!%))L"9"9"EXFF   F8
 yy(( C).>)B)B~&k<888MMMud++5555rK   c                   t          | t                    r|                     d          nd }t          |t                    ot          t	          |                    dd          pd                                                    oCt          t	          |                    dd          pd                                                    S )NrA  r   r9   rM  )r   rJ   r6  r   r5   r   )r>  rA  s     rL   "_xai_oauth_state_has_usable_tokensr    s    $.ud$;$;EUYYx   F64   	EVZZ339r::@@BBCC	EVZZ44:;;AACCDDrK   c                x   | r5t                      5  t                      }d d d            n# 1 swxY w Y   nt                      }t          |          }t          |          s,t          t	                                }t          |          r|}|st          dddd          |                    d          }t          |t                    st          dddd          t          |                    d	d
          pd
          
                                }t          |                    dd
          pd
          
                                }|st          dddd          |st          dddd          ||                    d          |                    d          pi |                    d          dS )Nz`No xAI OAuth credentials stored. Select xAI Grok OAuth (SuperGrok / Premium+) in `hermes model`.rT   xai_auth_missingTrL  rA  zGxAI OAuth state is missing tokens. Re-authenticate with `hermes model`.xai_auth_invalid_shaper   r9   rM  zMxAI OAuth state is missing access_token. Re-authenticate with `hermes model`.xai_auth_missing_access_tokenzNxAI OAuth state is missing refresh_token. Re-authenticate with `hermes model`.xai_auth_missing_refresh_tokenre  	discoveryr  )rA  re  r  r  )r  r4  r  r  r  rK  r6  r   rJ   r5   r   )r]  r=  r>  r  rA  r   rM  s          rL   _read_xai_oauth_tokensr    s5    ( 	, 	,)++J	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, &''
'
33E-e44 !23J3L3LMM-l;; 	! E 
n #!	
 
 
 	
 YYx  Ffd## 
U )!	
 
 
 	
 vzz."55;<<BBDDL

?B77=2>>DDFFM 
[ 0!	
 
 
 	
  
\ 1!	
 
 
 	
 		.11YY{++1r		.11	  rh  c                    |                      d          }t          |t                    o't          |                     d          t                    S )a  True when this store has its OWN ``providers.xai-oauth`` block.

    Distinguishes a profile that genuinely shadows the root xAI grant from
    one that only *reads* root via ``_load_provider_state``'s fallback. Only
    the latter needs the refresh write-through below.
    r  rT   )r6  r   rJ   )r=  r  s     rL    _profile_has_own_xai_oauth_stater  A  sA     {++Ii&&W:immK6P6PRV+W+WWrK   c                   t                      }|dS t          j                            d          rzt          j                            dd          }|rXt	          |          dz  dz  }	 |                    d          |                    d          k    rdS n# t          $ r Y dS w xY w	 |                                rt          |          }ni }t          |t                    sdS t          |d	t          |           d
           t          ||           dS # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)u  Persist a rotated xAI OAuth ``state`` into the global-root auth.json.

    Best-effort write-through for the multi-profile rotation hazard (#43589):
    xAI rotates the refresh_token on every refresh, so when a profile session
    refreshes a grant it resolved from the root fallback, the rotated chain
    must land back in root. Otherwise root keeps a now-revoked refresh token
    and every other profile reading the stale root grant dies with
    ``invalid_grant`` once its access token expires.

    Only updates ``providers.xai-oauth`` in the root store; never touches the
    profile store (the caller already saved that). Swallows all errors — a
    failed write-through degrades to the pre-existing behavior (root stale),
    it must never break the profile's own successful save.
    Nr  r  r9   r  r  Fr  rT   r$  z2xAI OAuth: write-through to global root failed: %s)r  r  r  r6  r   r  r   r  r4  r   rJ   r'  r  r   r*  )r>  r  r  r  r  r  s         rL   '_write_through_xai_oauth_to_global_rootr  L  s    )**K
 
z~~+,, 
vr22 	]++i7+EI&&e&44	8I8IQV8I8W8WWWF X   
P 	+K88LLL,-- 	FlKeQVWWWW{33333 P P PI3OOOOOOOOOPs0   *.B 
B)(B)-;D *0D 
E&EEr  r  re  rv  r  rv  c                  |Dt          j        t          j                                                                      dd          }t                      5  t                      }t          |           }t          |d          pi }| |d<   ||d<   ||d<   |r||d<   |r||d<   t          |d|           t          |           |rt          |           d d d            d S # 1 swxY w Y   d S )	Nr  r  rT   rA  re  rv  r  r  )r   r  r   r  r  r  r  r4  r  r5  r;  r  r  )rA  r  r  re  rv  r=  write_through_to_rootr>  s           rL   _save_xai_oauth_tokensr  y  sR    |HL11;;==EEhPSTT			 ; ;%''

 %EZ$P$P P$Z==C h ,n&k 	+!*E+ 	1$0E.!Ze<<<$$$  	;3E:::%; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;s   A?C!!C%(C%c                t   t          | t                    rd| vrdS 	 |                     d          }t          |          dk     rdS |d         }|dt          |           dz  z  z  }t	          j        t          j        |                    d                    	                    d                    }|
                    d	          }t          |t          t          f          sdS t          |          t          j                    t          d
t          |                    z   k    S # t          $ r Y dS w xY w)Nr  Fr  r#   r  r   r  ry  r  r   )r   r5   r  r   r"  r  r  r  r9  r  r6  r   r  r  r  r   )r   r  partspayload_b64r  r  s         rL   _xai_access_token_is_expiringr    s*   lC(( C|,C,Cu""3''u::>>5Ahss;///!344*V5k6H6H6Q6QRRYYZabbcckk%  #U|,, 	5SzzdikkC3|3D3D,E,EEFF   uus   (D) BD) 'AD) )
D76D7c                   t           }t          | t                    rd| vr|S 	 |                     d          }t	          |          dk     r|S |d         }|dt	          |           dz  z  z  }t          j        t          j        |	                    d                    
                    d                    }|                    d          }t          |t          t          f          s|S t          |          t          j                    z
  }|d	k    r|S |d
k    rt          d|          S |S # t           $ r |cY S w xY w)u.  How far before JWT ``exp`` to proactively refresh xAI OAuth tokens.

    SuperGrok sessions can still ship multi-hour access tokens, where the
    gateway-oriented :data:`XAI_ACCESS_TOKEN_REFRESH_SKEW_SECONDS` window
    makes sense. Device-code logins often return ~15-minute JWTs; applying
    the full hour-long skew to those forces a refresh on *every* credential
    resolution (chat turn, Imagine tool call, ``hermes auth status``, …),
    which burns single-use refresh tokens and races concurrent callers into
    ``invalid_grant`` quarantine.
    r  r  r#   r  r   r  ry  r  r   i
  r(   )%XAI_ACCESS_TOKEN_REFRESH_SKEW_SECONDSr   r5   r  r   r"  r  r  r  r9  r  r6  r   r  r  minr   )r   max_skewr  r  r  r  r  s          rL   #_xai_proactive_refresh_skew_secondsr    sI    5HlC(( C|,C,C""3''u::>>OAhss;///!344*V5k6H6H6Q6QRRYYZabbcckk%  #U|,, 	O#JJ,	>>OsH%%%   s*   )D1 BD1 .*D1 D1 /D1 1E ?E r   c               @   t          |           }|j        dk    rt          d| d| ddd          |j        pd                                }|st          d	| d
| ddd          |dk    r.|                    d          st          d	| d|ddd          | S )uI  Refuse any OIDC discovery endpoint that isn't HTTPS on the xAI origin.

    The OIDC discovery response is a long-lived, low-frequency request whose
    output is cached in ``~/.hermes/auth.json``. A single MITM during initial
    login could substitute a malicious ``token_endpoint``; that URL would
    then receive the refresh_token on every subsequent refresh — a permanent
    credential leak from a one-time MITM. Validating scheme + host pins the
    cached endpoint to the xAI auth origin (or a future ``*.x.ai`` subdomain
    if xAI migrates) so the cache poisoning loses its persistence guarantee.

    RFC 8414 §2 requires the issuer to be ``https://`` and SHOULD-keeps the
    token_endpoint on the same origin; we enforce both. ``x.ai`` is the
    bare apex, so we accept either exact host match or any ``.x.ai`` suffix.
    r  z(xAI OIDC discovery returned a non-HTTPS r;  r  rT   xai_discovery_invalidr:  r9   zxAI OIDC discovery z is missing a hostname: r  .x.aiz host z is not on the xAI origin (expected x.ai or a *.x.ai subdomain). Refusing to use a cached endpoint that may have been substituted by a MITM during initial discovery; re-authenticate with `hermes model` to re-fetch.)r   r  rK  r  r   r   )r  r   r  r  s       rL   _xai_validate_oauth_endpointr    s    c]]F}HuHHHHH (
 
 
 	

 O!r((**D 
I%IIIII (
 
 
 	

 v~~dmmG44~K% K Kt K K K !(
 
 
 	
 JrK   fallbackc                  | pd                                                     d          }|s|S 	 t          |          }n.# t          $ r! t                              d||           |cY S w xY w|j        dk    rt                              d||           |S |j        pd                                }|st                              d||           |S |dk    r4|	                    d          st                              d	|||           |S |S )
u  Refuse a non-xAI base_url for the OAuth-authenticated inference path.

    The xAI Grok OAuth bearer is a high-value, long-lived credential tied to
    the user's SuperGrok subscription. ``XAI_BASE_URL`` / ``HERMES_XAI_BASE_URL``
    let users repoint the inference endpoint (handy for staging or a local
    proxy), but the env override is also a credential-leak vector: a tampered
    ``.env`` or hostile shell init that sets
    ``XAI_BASE_URL=https://attacker.example/v1`` would ship the OAuth access
    token to a third party on every request, silently.

    Pin the inference origin to ``api.x.ai`` (or any ``*.x.ai`` subdomain xAI
    may add). On rejection, fall back to the default and log a warning rather
    than raise — a bad env var should not deadlock authentication, but it
    should also never leak the bearer.

    ``value`` is the already-stripped, trailing-slash-trimmed candidate from
    env. Empty input returns ``fallback`` unchanged.
    r9   rC  z>Ignoring malformed xAI base_url override %r; using %s instead.r  znRefusing non-HTTPS xAI base_url override %r (xai-oauth bearer would be sent in cleartext); falling back to %s.zEIgnoring xAI base_url override %r with no hostname; using %s instead.r  r  u   Refusing xAI base_url override %r — host %r is not on the xAI origin (expected x.ai or a *.x.ai subdomain). The xai-oauth bearer is only valid against xAI's inference API; sending it elsewhere would leak the credential. Falling back to %s.)
r   rE  r   r   r   r   r  r  r   r   )r   r  r  r  r  s        rL    _xai_validate_inference_base_urlr    sQ   & "##%%,,S11I )$$   Lx	
 	
 	
  }9x	
 	
 	

 O!r((**D Sx	
 	
 	
 v~~dmmG44~2 tX	
 	
 	
 s   ? (A*)A*c                   	 t          j        t          ddi|           }n(# t          $ r}t	          d| dd          |d }~ww xY w|j        dk    rt	          d	|j         d
dd          	 |                                }n(# t          $ r}t	          d| dd          |d }~ww xY wt          |t                    st	          ddd          t          |                    dd          pd          
                                }t          |                    dd          pd          
                                }|r|st	          ddd          t          |d           t          |d           ||dS )NrQ  r  )r!  r  zxAI OIDC discovery failed: rT   xai_discovery_failedr:  r#  z#xAI OIDC discovery returned status r  z*xAI OIDC discovery returned invalid JSON: xai_discovery_invalid_jsonz2xAI OIDC discovery response was not a JSON object.xai_discovery_incompleteauthorization_endpointr9   token_endpointz;xAI OIDC discovery response was missing required endpoints.r   )r  r  )r'  r6  XAI_OAUTH_DISCOVERY_URLr   rK  r)  r"  r   rJ   r5   r   r  )r  r`  r  r  r  r  s         rL   _xai_oauth_discoveryr  -  s   9#12#
 
 

    /#// '
 
 
 		 s""I(2FIII '
 
 
 	

--//   >>> -
 
 
 		 gt$$ 
@ +
 
 
 	

 !-Er!J!J!PbQQWWYY%5r::@bAAGGIIN! 
 
I +
 
 
 	

 !!7?WXXXX 7GHHHH"8(  s,   ! 
AAA0B 
B*B%%B*r  r  r  c          	     :   ~ t          |t                    r|                                st          dddd          |                                pt	          |          d         }t          |d           t          j        t          dt          |                              }t          j
        |d	d
i          5 }|                    |ddidt          |d          }d d d            n# 1 swxY w Y   |j        dk    rh|j                                        }|j        dk    r t          d|rd| ndz   dz   ddd          t          d|rd| ndz   dd|j        dv           	 |                                }	n(# t           $ r}
t          d|
 dd          |
d }
~
ww xY wt          |	t"                    st          ddd d          t          |	                    d!d          pd                                          }|st          d"dd#d          |t          |	                    d          p|                                          t          |	                    d$          pd                                          |	                    d%          t          |	                    d&          pd'                                          pd't'          j        t*          j                                                                      d(d)          d*}|S )+NzHxAI OAuth is missing refresh_token. Re-authenticate with `hermes model`.rT   r  TrL  r  r  r  rQ  r  r}  r  rO  rM  rS  r<   rM  r~  r#  r  z'xAI token refresh failed with HTTP 403.rW  r9   uP   This OAuth account is not authorized for xAI API access — xAI may be restricting API/OAuth use to specific SuperGrok tiers despite the in-app subscription being active. Re-logging in won't change that; set ``XAI_API_KEY`` and switch to ``provider: xai`` (API-key path) if available, or upgrade your subscription at https://x.ai/grok.xai_oauth_tier_deniedFzxAI token refresh failed.xai_refresh_failed>   rV  r  z)xAI token refresh returned invalid JSON: xai_refresh_invalid_jsonr:  z1xAI token refresh response was not a JSON object.xai_refresh_invalid_responser   z4xAI token refresh response was missing access_token. xai_refresh_missing_access_tokenid_tokenr  rZ  r[  r  r  )r   rM  r  r  rZ  re  )r   r5   r   rK  r  r  r'  r  r  r  r  r(  XAI_OAUTH_CLIENT_IDr)  r  r"  r   rJ   r6  r   r  r   r  r  r  )r   rM  r  r  endpointr  r  r`  r  r  r  r  r  s                rL   refresh_xai_oauth_purer  ^  s    	mS)) 
1D1D1F1F 
V 1!	
 
 
 	
 ##%%`)=o)N)NO_)`H !1ABBBBmCU?%;%;<<==G	g:L/M	N	N	N 	
RX;;#%HI-0!.   
 
	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 s""$$&& 3&&9-3;))))=EE %,!&    ')/7%V%%%R9 %&2j@
 
 
 	
--//   === +
 
 
 		 gt$$ 
? /!	
 
 
 	
 7;;~r::@bAAGGII 
B 3!	
 
 
 	
 )W[[99J]KKQQSSJ//5266<<>>kk,//'++l33?x@@FFHHTH X\22<<>>FFxQTUU G Ns*   ?$C//C36C3.F 
F(F##F()r  c          	        	 t          t                      d          pi }t          |                    d          pd          }n# t          $ r d}Y nw xY wt          t          |                     dd          pd          t          |                     dd          pd          ||          }t          |           }|d         |d<   |d         |d<   |                    d          r|d         |d<   |                    d	          |d	         |d	<   |                    d
          r|d
         |d
<   t          |d|i||d         |           |S )NrT   rv  rN   r   r9   rM  r  r  r  rZ  r  re  r  )r5  r4  r5   r6  r   r  rJ   r  )rA  r  r  r  r>  rv  rc  r  s           rL   _refresh_xai_oauth_tokensr    s   ($%5%7%7EEK		+..E2EFF		 ( ( ('			(&FJJ~r**0b11FJJ++1r22%'	  I &\\N%.~%>N>"&/&@N?#}}Z   ;%.z%:z"}}\"".'0'>|$}}\"" ?'0'>|$#^4!~.    s   AA AAc           
     
   t                      }t          |d                   }t          |                    dd          pd                                          }t          dd          }t          |                    d          pi           }t          |                    dd          pd                                          }t          |                    dd          pd                                          }	|t          |          nt          |          }
t          |           }|s|rt          ||
          }|r t          t          t          t                    |d	z             
          5  t          d          }t          |d                   }t          |                    dd          pd                                          }t          |                    d          pi           }t          |                    dd          pd                                          }t          |                    dd          pd                                          }	|t          |          nt          |          }
t          |           }|s|rt          ||
          }|r|st          |          d         }	 t          |||	|          }t          |                    dd          pd                                          }n=# t           $ r/}t#          |          r	 t%                      }t'          |d          pi }t          |                    d          pi           }|                    dd            |                    dd            ||d<   d|j        pdt          |          ddt-          j        t0          j                                                  d|d<   t7          |d|d           t9          |           n2# t:          $ r%}t<                              d|           Y d }~nd }~ww xY w d }~ww xY wd d d            n# 1 swxY w Y   tA          tC          j"        dd                                          #                    d          p9tC          j"        dd                                          #                    d          tH                    }d||d|                    d          ddS )NrA  r   r9   "HERMES_XAI_REFRESH_TIMEOUT_SECONDSr  r  r  r  r  r  Fr\  )r  r  r  rT   rM  r  r  Tr  r	  r$  z2xAI OAuth: failed to persist quarantined state: %sHERMES_XAI_BASE_URLrC  r   r  r  re  rN   r  )%r  rJ   r5   r6  r   r!   r   r  r   r  r  r  r  r  r  r  rK  $_is_terminal_xai_oauth_refresh_errorr4  r5  r[  rN  r   r  r   r  r  r'  r  r   r   r*  r  r  r  rE  DEFAULT_XAI_OAUTH_BASE_URL)rh  ri  rj  r?  rA  r   r  r  r  r  effective_skewrp  r  _q_store_q_state	_q_tokensr  r$  s                     rL   %resolve_xai_oauth_runtime_credentialsr    sa    "##D$x.!!Fvzz."55;<<BBDDL'(LbQQTXXk**0b11I'7<<BCCIIKKNtxx339r::@@BBL  + 	 !!!0>> 
 -((N U 3 U6|^TT 5c%8Q2R2RTknqTq.r.rsss 4	 4	)666D$x.))Fvzz."==CDDJJLLLTXXk228b99I /?!D!D!JKKQQSSNtxx;;ArBBHHJJL (3 ()))8FF 
 "-00N" ](; ]!>|^!\!\ %% e%9:Q%R%RSc%dN"6'5%1(?	  F $'vzz."'E'E'K#L#L#R#R#T#TLL    ;C@@ '7'9'9H';Hk'R'R'XVXH(,X\\(-C-C-Ir(J(JI%MM.$???%MM/4@@@1:HX.,7(+(H4H+.s88*C48&.l8<&@&@&J&J&L&L; ;H%67 2(K^cdddd,X6666(   "LL TV_        554	 4	 4	 4	 4	 4	 4	 4	 4	 4	 4	 4	 4	 4	 4	l 0
	',,2244;;C@@ 	=9^R((..0077<<+  H  %00 )
 
 
sc   6EQA
LQQ	Q+C%PQ
Q P;6Q;Q  QQ		QQQbool | ssl.SSLContextc                     t           j        dk    r<	 ddl} t          j        |                                           S # t          $ r Y nw xY wdS )a~  Platform-aware default SSL verify for httpx clients.

    On macOS with Homebrew Python, the system OpenSSL cannot locate the
    system trust store and valid public certs fail verification. When
    certifi is importable we pin its bundle explicitly; elsewhere we
    defer to httpx's built-in default (certifi via its own dependency).
    Mirrors the weixin fix in 3a0ec1d93.
    darwinr   NcafileT)rI  rJ  certifisslcreate_default_contextwherer  )r  s    rL   _default_verifyr  K  s`     |x	NNN-W]]__EEEE 	 	 	D	4s   *= 
A
	A
insecure	ca_bundle
auth_stater  Optional[bool]r  r  c                   t          |t                    r|                    d          ni }t          |t                    r|ni }| t          | d          n$t          |                    dd          d          }|pP|                    d          p;t	          j        d          p't	          j        d          pt	          j        d          }|rdS |rlt          |          }t          j                            |          s)t          
                    d	|           t                      S t          j        |
          S t                      S )NtlsFdefaultr  r  HERMES_CA_BUNDLESSL_CERT_FILEREQUESTS_CA_BUNDLEuJ   CA bundle path does not exist: %s — falling back to default certificatesr  )r   rJ   r6  r"   r  r  r5   r  isfiler   r   r  r  r  )r  r  r  	tls_stateeffective_insecureeffective_caca_paths          rL   _resolve_verifyr(  ]  s\    *4J)E)EM
u%%%2I'	488@		bI 5=4H%0000Y]]:u==uMMM 
 	 	+==%%	+9'((	+ 9_%%	+ 9)**   u :l##w~~g&& 	%NN\   #$$$)9999rK   r  httpx.Clientr:   c                   |                      | dd|i|rd|ini           }|                                 |                                g d}fd|D             }|r%t          dd                    |                     S )	zFPOST to the device code endpoint. Returns device_code, user_code, etc.z/api/oauth/device/coder<   r=   r?  r&   	user_codeverification_uriverification_uri_completer  intervalc                    g | ]}|v|	S rA   rA   )r   fr?  s     rL   rG  z(_request_device_code.<locals>.<listcomp>  s    ;;;QQd]]q]]]rK   z%Device code response missing fields: , )r(  raise_for_statusr"  r   ry  )r  r:   r<   r=   r`  required_fieldsmissingr?  s          @rL   _request_device_coder7    s     {{222
#(0b
   H ==??D  O <;;;/;;;G WU7ASASUUVVVKrK   r  c                &   t          j                    t          d|          z   }t          dt          |t                              }t          j                    |k     r$|                     | dd||d          }|j        dk    r)|                                }	d|	vrt          d          |	S 	 |                                }
n1# t          $ r$ |
                                 t          d	          w xY w|
                    d
d          }|dk    rt          j        |           |dk    r)t          |dz   d          }t          j        |           |
                    d          pd}t          | d|           t          d          )zDPoll the token endpoint until the user approves or the code expires.r#   /api/oauth/token,urn:ietf:params:oauth:grant-type:device_coderS  r<   r&   r+  r#  r   z+Token response did not include access_tokenz1Token endpoint returned a non-JSON error responserZ  r9   authorization_pending	slow_down   r  zUnknown authentication errorr;  z*Timed out waiting for device authorization)r  r  r  r  %DEVICE_AUTH_POLL_INTERVAL_CAP_SECONDSr(  r)  r"  r   r   r4  r  r6  r  r  )r  r:   r<   r&   r  r  r  current_intervalr`  r  error_payload
error_codedescriptions                rL   _poll_for_tokenrD    s    ~#a"4"44H1c-1VWWXX
.

X
%
%;;000L&*   
 
 3&&mmooGW,, !NOOON	T$MMOOMM 	T 	T 	T%%'''RSSS	T #&&w33
000J'((($$"#3a#7<<J'(((#''(;<<^@^j99K99:::
C
D
DDs   3C .C6znous_auth.jsonc                     t          j        dd                                          } | r!t          |                                           S ddlm}  |            dz  S )uz  Resolve the directory that holds the shared Nous token store.

    Honors ``HERMES_SHARED_AUTH_DIR`` so tests can redirect it to a tmp
    path without touching the real user's home. Defaults to
    ``<hermes-root>/shared/``, where ``<hermes-root>`` is what
    :func:`hermes_constants.get_default_hermes_root` returns — so
    Linux/macOS classic installs land at ``~/.hermes/shared/``, native
    Windows installs at ``%LOCALAPPDATA%\hermes\shared\``, and
    Docker / custom ``HERMES_HOME`` deployments at
    ``<HERMES_HOME>/shared/``. Sits outside any named profile so all
    profiles under the same root share the store.
    HERMES_SHARED_AUTH_DIRr9   r   r  shared)r  r  r   r   r  r  r  )overrider  s     rL   _nous_shared_auth_dirrI    sh     y1266<<>>H +H~~((***888888""$$x//rK   c                 T   t                      t          z  } t          j                            d          rrddlm}  |            dz  t          z                      d          }	 |                     d          }n# t          $ r | }Y nw xY w||k    rt          d|  d          | S )	Nr  r   r  rG  Fr  zDRefusing to touch real user shared Nous auth store during test run: z@. Set HERMES_SHARED_AUTH_DIR to a tmp_path in your test fixture.)
rI  NOUS_SHARED_STORE_FILENAMEr  r  r6  r  r  r  r   r  )r  r  real_home_sharedr  s       rL   _nous_shared_store_pathrM    s     ""%??D 
z~~+,, <<<<<<##%%03MM
''

 		||5|11HH 	 	 	HHH	'''ZZ Z Z   Ks   &A= =BBc              #     K   	 t                                          d          }n# t          $ r dV  Y dS w xY wt          |t          | d          5  dV  ddd           dS # 1 swxY w Y   dS )a  Cross-profile lock for the shared Nous OAuth store.

    Lock ordering invariant: if both this and ``_auth_store_lock`` need
    to be held, acquire ``_auth_store_lock`` FIRST. All runtime refresh
    paths follow this order. The one exception is
    ``_try_import_shared_nous_state``, which holds this lock alone for
    the entire refresh cycle so concurrent imports on sibling profiles
    can't race on the single-use shared refresh token; that helper must
    NOT be called with ``_auth_store_lock`` already held.
    r  Nz+Timed out waiting for shared Nous auth lock)rM  r  r  r  _nous_shared_lock_holder)r  r  s     rL   _nous_shared_store_lockrP    s      +--99'BB		   
 
 5	
 
   	                 s   !& 88A$$A(+A(c                4   t                      }|sdS |                    d          }t          |t                    r|                                sdS |                     d          }t          |                    d                    pd}t          |                     d                    pd}|                                t          |pd                                          k    }||k    }|s|sdS dD ] }|                    |          }	|	dvr|	| |<   !dS )	zACopy fresher shared OAuth tokens into a profile-local Nous state.FrM  r          r9   )	r   rM  rZ  r=   r<   r:   r;   r!  r  >   Nr9   T)_read_shared_nous_stater6  r   r5   r   r  )
r>  rG  shared_refreshlocal_refreshshared_access_explocal_access_exprefresh_changedfresher_accessr  r   s
             rL   _merge_shared_nous_oauth_staterZ  4  s.   $&&F uZZ00Nnc** .2F2F2H2H uIIo..M,VZZ-E-EFFM#+EIIl,C,CDDK$**,,M4GR0H0H0N0N0P0PPO&)99N > u
   

3
""E#J4rK   c                   |                      d          }|                      d          }t          |t                    r|                                sdS t          |t                    r|                                sdS d|||                      d          pd|                      d          pt          |                      d          pt
          |                      d	          pt          |                      d
          pt          |                      d          |                      d          t          j	        t          j                                                  d}	 t                      5  t                      }|j                            dd           t#          |           |                    |j         dt)          j                     dt-          j                    j                   }t)          j        t          |          t(          j        t(          j        z  t(          j        z  t:          j        t:          j        z            }	 t)          j         |dd          5 }|!                    tE          j#        |dd                     |$                                 t)          j%        |&                                           ddd           n# 1 swxY w Y   t)          j'        ||           	 |(                                r|)                                 nO# tT          $ r Y nCw xY w# 	 |(                                r|)                                 w w # tT          $ r Y w w xY wxY wddd           n# 1 swxY w Y   tW          dt          |          tY          |                     dS # tZ          $ r&}t\          /                    d|           Y d}~dS d}~ww xY w)ao  Persist a minimal copy of the Nous OAuth state to the shared store.

    Best-effort: any failure is swallowed after logging. The shared store
    is a convenience layer; the per-profile auth.json remains the source
    of truth.

    We deliberately omit the runtime ``agent_key`` compatibility field;
    the OAuth tokens are the cross-profile source of truth.
    rM  r   Nr#   rZ  r[  r=   r<   r:   r;   r!  r  )_schemar   rM  rZ  r=   r<   r:   r;   r!  r  r  Tr  r  r  r  ry  r  r  rC  nous_shared_store_written)r  refresh_token_fpz*Failed to write shared Nous auth store: %s)0r6  r   r5   r   DEFAULT_NOUS_SCOPEDEFAULT_NOUS_CLIENT_IDr  DEFAULT_NOUS_INFERENCE_URLr   r  r   r  r  rP  rM  r  r  r   r  r7   r  r  r  r  r  r  r  r   r  r  r  r  r  r  r"  r  r  r  r  r  r  r
  r  r  r{  r   r   r*  )	r>  rM  r   rG  r  tmpr  rD  r  s	            rL   _write_shared_nous_staterc  W  s"    IIo..M99^,,L}c** }/B/B/D/D |S)) l.@.@.B.B  $&ii--97##9'9YY{++E/E 99%677R;R#ii(<==[A[yy//ii--l8<00::<< F!H$&& 	 	*,,DKdT:::d###..DI!T!TBIKK!T!T$*,,BR!T!TUUC Cbj(294t|+ B
Yr3999 *RHHTZqDIIIJJJHHJJJHRYY[[)))* * * * * * * * * * * * * * * 
3%%%zz|| %

   Dzz|| %

%   D1	 	 	 	 	 	 	 	 	 	 	 	 	 	 	4 	'T/>>	
 	
 	
 	
 	
 	

  H H HA3GGGGGGGGGHs   !N7 /C!M>L3(A%KL3K	L3 K	!L3:(L#"M>#
L0-M>/L00M>3M/5(MM/
M,	)M/+M,	,M//M>2N7 >NN7 N/N7 7
O'O""O'c                 h   	 t                      } n# t          $ r Y dS w xY w|                                 sdS 	 t          j        |                                           }n;# t          t          f$ r'}t          	                    d| |           Y d}~dS d}~ww xY wt          |t                    sdS |                    d          }|                    d          }t          |t                    r|                                sdS t          |t                    r|                                sdS |S )u  Return the shared Nous OAuth state if present and well-formed.

    Returns ``None`` when the file is missing, unreadable, malformed, or
    lacks required fields. Callers should treat ``None`` as "no shared
    credentials available — fall through to device-code".
    Nz.Shared Nous auth store at %s is unreadable: %srM  r   )rM  r  r  r"  r  r  r  r   r   r*  r   rJ   r6  r5   r   )r  r  r  rM  r   s        rL   rS  rS    sI   &((   tt <<>> t*T^^--..Z    EtSQQQttttt gt$$ tKK00M;;~..L}c** }/B/B/D/D t|S)) l.@.@.B.B tNs#    
&A   B1BBc                D   	 t                      5  t                      }	 |                                 n# t          $ r Y nw xY wddd           n# 1 swxY w Y   t	          d|            dS # t
          $ r&}t                              d|           Y d}~dS d}~ww xY w)zBRemove the shared Nous OAuth store after a terminal token failure.Nnous_shared_store_clearedr  z*Failed to clear shared Nous auth store: %s)rP  rM  r
  FileNotFoundErrorr  r   r   r*  )r  r  r  s      rL   _clear_shared_nous_stateri    s   	H$&& 	 	*,,D$   		 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	0@@@@@@ H H HA3GGGGGGGGGHsY   A/ A5A
AAAAA/ AA/ AA/ /
B9BBr  c                |    t          | t                    o'| j        dk    o| j        dv ot	          | j                  S )z>True when retrying the same Nous refresh token cannot succeed.rM      r  r  r  r   rK  rM  rN  r   rO  r  s    rL   _is_terminal_nous_refresh_errorrn    sJ     	3	"" 	'LF"	'HRR	' %&&	rK   c                |    t          | t                    o'| j        dk    o| j        dv ot	          | j                  S )u  True when retrying the same xAI OAuth refresh token cannot succeed.

    ``xai_refresh_failed`` covers HTTP 400/401/403 from the token endpoint
    (invalid_grant, token revoked, refresh_token_reused).
    ``xai_auth_missing_refresh_token`` means the pool entry has no refresh
    token at all — retrying will never work.
    Both carry ``relogin_required=True``; transient failures (429, 5xx) do not.
    rT   >   r  r  rl  rm  s    rL   r  r    sJ     	3	"" 	'LK'	'HPP	' %&&	rK   c                |    t          | t                    o'| j        dk    o| j        dv ot	          | j                  S )u  True when retrying the same Codex OAuth refresh token cannot succeed.

    ``codex_refresh_failed`` covers HTTP 400/401/403 from the token endpoint
    (invalid_grant, token revoked, refresh_token_reused).
    ``codex_auth_missing_refresh_token`` means the pool entry has no refresh
    token at all — retrying will never work.
    Both carry ``relogin_required=True``; transient failures (429, 5xx) do not.
    rO   >   r  r  r  r  rd  rl  rm  s    rL   &_is_terminal_codex_oauth_refresh_errorrq    sN     	3	"" 		'LN*		'H 
 
		' %&&rK   c                  dD ]}|                      |d           d|j        t          |          |dt          j        t
          j                                                  d| d<   t          |           t                       dS )zKKeep routing metadata but remove dead OAuth material so it is not replayed.)r   rM  r  r  r!  r$  r%  r&  r'  r(  r#  NrM   Tr  r	  )
r[  rN  r5   r   r  r   r  r  ri  !invalidate_nous_auth_status_cache)r>  rZ  r  r  s       rL   _quarantine_nous_oauth_statert    s       			#t
u:: l8<((2244   E
 V$$$%'''''rK   c                  |                      d          }t          |t                    sdS |                     d          }t          |t                    sdS g }d}t          dt           h}|D ]F}t          |t                    r|                     d          |v rd}1|                    |           G|r||d<   t          d||j                   |S )	zHRemove singleton-seeded Nous pool entries that contain dead OAuth state.r  FrM   zmanual:r  T!nous_pool_device_code_quarantined)r  rB  )r6  r   rJ   r4  NOUS_DEVICE_CODE_SOURCErI  r  rN  )	r=  rZ  r  r	  r?  retainedrJ  singleton_sourcesr
  s	            rL   _quarantine_nous_pool_entriesrz    s    >>+,,DdD!! uhhvGgt$$ uHG02U<S2U2UV  eT"" 	uyy':':>O'O'OG 
V/z	
 	
 	
 	

 NrK   c                n   	 t          t          | dz   t                              5  t                      }|s	 ddd           dS |                    d          |                    d          |                    d          pt
          |                    d          pt          |                    d          pt          |                    d	          pd
|                    d          pt          |                    d          |                    d          dddddd}d d}t          || d|          }t          |           ddd           n# 1 swxY w Y   n# t          $ rw}t          dt          |          j        t          |dd                     t!          |          rt#          d           t$                              d|           Y d}~dS d}~wt(          $ rI}t          dt          |          j                   t$                              d|           Y d}~dS d}~ww xY w|S )!u  Attempt to rehydrate Nous OAuth state from the shared store.

    Reads the shared file (if present), runs a forced refresh using the
    stored refresh_token to produce a fresh inference JWT scoped to this
    profile, and returns the full auth_state dict ready
    for ``persist_nous_credentials()``.

    Returns ``None`` when no shared state is available or the rehydrate
    fails for any reason (expired refresh_token, portal unreachable,
    etc.) — caller should then fall through to the normal device-code
    flow.
    r  r  Nr   rM  r<   r:   r;   rZ  r[  r=   r!  r  Fr  r  r   rM  r<   r:   r;   rZ  r=   r!  r  r$  r&  r  updated_stater?   _reasonr5   r   rR  c                $    t          |            d S rT  )rc  )r~  r  s     rL   _persist_shared_refreshz>_try_import_shared_nous_state.<locals>._persist_shared_refreshb  s    (77777rK   Tr  rh  on_state_updatenous_shared_import_failedrN  )
error_typerB  &shared_import_terminal_refresh_failurezShared Nous import failed: %s)r  )r~  r?   r  r5   r   rR  )rP  r  r  rS  r6  r`  r  ra  r_  refresh_nous_oauth_from_staterc  rK  r  r  rE   r  rn  ri  r   r*  r   )r  rG  r>  r  rc  r  s         rL   _try_import_shared_nous_stater  :  s    2$S39NPi5j5jkkk  	0  	0,..F  	0  	0  	0  	0  	0  	0  	0  	0 !'

> : :!'O!<!<#ZZ44N8N#)::.?#@#@#[D[&,jj1E&F&F&dJd$jj66B(G,,B0B%zz-88$jj66!(,$)==% %E8 8 8 8 6 /" 7	  I %Y///A 	0  	0  	0  	0  	0  	0  	0  	0  	0  	0  	0  	0  	0  	0  	0B  	 	 	'Cyy)sFD11	
 	
 	
 	

 +3// 	O$%MNNN4c:::ttttt   'Cyy)	
 	
 	
 	
 	4c:::ttttt sS   &E  EE  DEE  EE  EE   
H2*A,GH2)>H--H2c                0   |                      | dd|id|d          }|j        dk    r-|                                }d|vrt          dd	d
d          |S 	 |                                }n%# t          $ r}t          dd	d          |d }~ww xY wt          |                    dd                    }t          |                    d          pd          }	|dv }
|	                                }|dk    sd|v sd|v rd}	d}
t          |	d	||
          )Nr9  zx-nous-refresh-tokenrM  )rS  r<   r~  r#  r   z%Refresh response missing access_tokenrM   r  TrL  zRefresh token exchange failedrM  rO  rZ  r  r  rk  r  reusezreuse detectedu  Nous Portal detected refresh-token reuse and revoked this session.
This usually means an external process (monitoring script, custom self-heal hook, or another Hermes install sharing ~/.hermes/auth.json) called POST /api/oauth/token with Hermes's refresh token without persisting the rotated token back.
Nous refresh tokens are single-use — only Hermes may call the refresh endpoint. For health checks, use `hermes auth status` instead.
Re-authenticate with: hermes auth add nous)r(  r)  r"  rK  r   r5   r6  r   )r  r:   r<   rM  r`  r  rA  r  rN  rC  reloginlowereds               rL   _refresh_access_tokenr    s    {{,,,'7)"
 
   H s""--//((C%+/TXZ Z Z ZI  I I I7!'$@ @ @EH	II }  /::;;Dm''(;<<_@_``KPPG !!G%%%G););?OSZ?Z?Z9 	 
K&tg
V
V
VVs   A/ /
B9BB)r  verifyr;   r  
bool | strc                x   t          j        |          }t          j        |ddi|          5 }|                    |                     d           ddd| i          }d	d	d	           n# 1 swxY w Y   |j        d
k    rd|j         }	 |                                }t          |                    d          p|                    d          p|          }n2# t          $ r%}	t          
                    d|	           Y d	}	~	nd	}	~	ww xY wt          |dd          |                                }
|
                    d          }t          |t                    sg S g }|D ]}t          |t                    s|                    d          }t          |t                    rT|                                r@|                                }d|                                v r|                    |           dd}|                    |           t          t                              |                    S )z6Fetch available model IDs from the Nous inference API.rQ  r  r  r!  r  rC  z/modelsr  r  )r!  Nr#  z#/models request failed with status r  rZ  z'Could not parse error response JSON: %srM   models_fetch_failedr:  r?  r6   hermesmidr5   r   rB   c                j    |                                  }d|v rd| fS d|v rd|vrd| fS d|v rd| fS d| fS )Nopusr   prosonnetr#      r  )r   )r  lows     rL   _model_priorityz*fetch_nous_models.<locals>._model_priority  sX    iikkS==s8OC<<HC//s8Os??s8O3xrK   r  )r  r5   r   rB   )r'  r  r  r6  rE  r)  r"  r5   r   r   r*  rK  r   r4  rJ   r   r   rI  sortfromkeys)r;   rQ   r  r  r  r  r`  rC  r  r  r  r?  	model_idsr   model_idr  r  s                    rL   fetch_nous_modelsr    s    mO,,G	g:L/MV\	]	]	] 
ag::!((--666$&9&9&9:  
 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 s""RH<PRR	G--//Ccgg&9::]cggg>N>N]R]^^KK 	G 	G 	GLLBAFFFFFFFF	Gf;PQQQQmmooG;;vDdD!! 	I 	" 	"$%% 	88D>>h$$ 	")9)9 	"..""C399;;&&S!!!    NNN'''i(()))s*   3A--A14A1AC 
D&DDc                *   |                      d          }t          |t                    r|                                sdS t	          ||                      d          |                      d          t          dt          |                              S )Nr$  Fr=   r&  r   r  )r6  r   r5   r   r  r  r   )r>  r  r  s      rL   _agent_key_is_usabler    s    
))K
 
 Cc3 syy{{ u%ii  99344As?3344	   rK   )r  r  r  rj  c           
     B	   t                      5  t                      }t          |d          \  }}|st          ddd          t	                      }|r|                    d          }nt          |                    d                    pt                              d          }t          |          }	|	j
        r6|	j
        t          vr(t                              d||	j
                   t          }t          |                    d          pt                    }
t!          |||	          }t#          t%          | d
z   t&                              5  t)          |          }|                    d          }|                    d          }t+          |t                    r|st          ddd          t-          |                    d          |          s.|rt/          |d||           |cddd           cddd           S t+          |t                    r|st          ddd          t1          j        | r| nd          }t1          j        |ddi|          5 }	 t7          |||
|          }nX# t          $ rK}t9          |          r6t;          ||d           t=          ||d           t/          |d||            d}~ww xY w	 ddd           n# 1 swxY w Y   t?          j         tB          j"                  }tG          |                    d                    }|d         |d<   |                    d          p||d<   |                    d          p|                    d          pd|d<   |                    d          p|                    d          |d<   |$                                |d<   ||d<   t?          j%        |&                                |z   tB          j"                  $                                |d<   ||d<   |
|d<   |du t+          |t                    r|ndd |d!<   t/          |d||           tO          |           |d         cddd           cddd           S # 1 swxY w Y   	 ddd           dS # 1 swxY w Y   dS )"zKResolve a refresh-aware Nous Portal access token for managed tool gateways.rM   &Hermes is not logged into Nous Portal.Tr  rC  r:   Sauth: ignoring invalid portal_base_url %r (host %r not in allowlist), using defaultr<   r  r  r  r   rM  ,No access token found for Nous Portal login.r  Nz2Session expired and no refresh token is available.r$   rQ  r  r  r  r:   r<   rM  $managed_access_token_refresh_failurerg  r  rZ  r[  r=   r!  r  Fr|  r  )(r  r4  r  rK  r  rE  r  r6  r  r   r  r  r   r   r5   r`  r(  rP  r  r  rZ  r   r  r#  r'  r  r  r  rn  rt  rz  r   r  r   r  r  r  r  r  rc  )r  r  r  rj  r=  r>  state_source_pathenv_portal_overrider:   parsed_portal_urlr<   r  merged_sharedr   rM  r  r  rc  r  r  
access_ttls                        rL   resolve_nous_access_tokenr     s{    
		 m) m)%''
#CJPV#W#W   	8!%    899 	:188==OO #599->#?#?@@ +*fSkk 
 !) 9 9 ) :.?.HPj.j.ji#%6%?   #:		+..H2HII	 (iTYZZZ$S39NPi5j5jkkk G	) G	):5AAM 99^44L!IIo66MlC00  B#%)     		, 7 79MNN $  a2:vuN_```#G	) G	) G	) G	) G	) G	) G	)Mm) m) m) m) m) m) m) m)l mS11  H#%)    m$POODQQG!#56     5%(7"+&3	! ! !II !   6s;; e4!#I   
 6&#I   
 7z65Rcddd               6 ,x|,,C,Y]]<-H-HIIJ$-n$=E.!%.]]?%C%C%T}E/""+--"="="d<AXAX"d\dE,&]]733Iuyy7I7IE'N#&==??E- ",E,"*"8*,<# # # ikk , (7E#$!*E+"eO'1&#'>'>HVVD E%L +:vuFWXXX$U+++(OG	) G	) G	) G	) G	) G	) G	)Mm) m) m) m) m) m) m) m)LG	) G	) G	) G	) G	) G	) G	) G	) G	)Mm) m) m) m) m) m) m) m) m) m) m) m) m) m) m) m) m) m)s   ERBQ;/RAQ;"K$I87K8
K	AK	K	KQ;K!!Q;$K!%E=Q;"R;Q?	?RQ?	RRRr[  rZ  r=   r!  r  r$  r&  r  r  r  rh  r  rZ  r$  r&  r  /Optional[Callable[[Dict[str, Any], str], None]]c               *   | ||pt           |pt                              d          |pt                              d          |pd|pt          |||	|
t          |          |dd}t          |||          }t          j        |r|nd          }t          j	        |ddi|	          5 }t          |                    d
          |                    d          |                    d                    }|s||                    d          }t          |t                    r|s+|t          d| dd|d          t          ddd          t          ||d         |d         |          }t!          j        t$          j                  }t)          |                    d                    }|d
         |d
<   |                    d          p||d<   |                    d          p|                    d          pd|d<   |                    d          p|                    d          |d<   t+          |                    d                    }|pt          |d<   |                                |d<   ||d<   t!          j        |                                |z   t$          j                                                  |d<   | |t3          |          d           t5          |           t7          |           ddd           n# 1 swxY w Y   |S ) a  Refresh Nous OAuth state without mutating auth.json directly.

    ``on_state_update`` is called after a successful access-token refresh.
    Callers that own persistent state can use it to save the newly rotated
    refresh token before later validation can fail.
    rC  r[  r|  r}  r  r$   rQ  r  r  r   r=   r  r  NrM  r  O) and no refresh token is available. Re-authenticate with: hermes auth add nousrM   TrL  z.No refresh token is available for Nous Portal.r  r:   r<   r  r  rZ  r;   r!  r  post_refresh_access_token)r`  r  rE  ra  r_  r   r(  r'  r  r  r  r6  r   r5   rK  r  r   r  r   r  r  r  r  r  r  rJ   r  r-  )r   rM  r<   r:   r;   rZ  r=   r!  r  r$  r&  r  r  r  rh  r  r>  r  r  r  current_invoke_jwt_statusrefresh_token_valuerc  r  r  refreshed_urls                             rL   refresh_nous_oauth_purer  x  s   4 %&8"8+F/FNNsSS1O5OWWX[\\ ,H,,"  4X"
 
 E" h)PUVVVFmHOODIIG	g:L/MV\	]	]	] 5'ag$;IIn%%))G$$yy..%
 %
 %
!
  ,	J5A"'))O"<"<1377 ?R ,8#E5E E E "(6)-     D#%)   
 . %&7 8,1	  I ,x|,,C,Y]]<-H-HIIJ$-n$=E.!%.]]?%C%C%ZGZE/""+--"="="d<AXAX"d\dE,&]]733Iuyy7I7IE'N FimmThFiFijjM*7*U;UE&'#&==??E- ",E,"*"8*,# # #ikk , *U-HIII)%000&&&k5' 5' 5' 5' 5' 5' 5' 5' 5' 5' 5' 5' 5' 5' 5'n Ls   'ILLLr  c                  |                      d          pi }t          |                      dd          |                      dd          |                      dd          |                      dt                    |                      dt                    |                      d	d
          |                      dt                    |                      d          |                      d          |                      d          |                      d          ||                     d          |                     d          ||          S )zRRefresh Nous OAuth from a state dict. Thin wrapper around refresh_nous_oauth_pure.r  r   r9   rM  r<   r%   r:   r;   rZ  r[  r=   r!  r  r$  r&  r  r  r  )r6  r  r  ra  r_  )r>  r  rh  r  r  s        rL   r  r    s    ))E


 bC"		."%%		/2&&		+|,,		#%<==		&(BCC99\844ii!344IIm,,99\**))K(("YY'=>>'$$''+&&#'!   rK   )r%  c                  ddl m} t          |           }|rEt          |                                          r$t          |                                          |d<   t                      5  t                      }t          |d|           t          |           ddd           n# 1 swxY w Y   t          |            |d          }t          d |                                D             d          S )u-  Persist Nous OAuth credentials as the singleton provider state
    and ensure the credential pool is in sync.

    Nous credentials are read at runtime from two independent locations:

    - ``providers.nous``: singleton state read by
      ``resolve_nous_runtime_credentials()`` during 401 recovery and by
      ``_seed_from_singletons()`` during pool load.
    - ``credential_pool.nous``: used by the runtime ``pool.select()`` path.

    Historically ``hermes auth add nous`` wrote a ``manual:device_code`` pool
    entry only, skipping ``providers.nous``. When the runtime credential
    expired, the recovery path read the empty singleton state and raised
    ``AuthError`` silently (``logger.debug`` at INFO level).

    This helper writes ``providers.nous`` then calls ``load_pool("nous")`` so
    ``_seed_from_singletons`` materialises the canonical ``device_code`` pool
    entry from the singleton.  Re-running login upserts the same entry in
    place; the pool never accumulates duplicate device_code rows.

    ``label`` is an optional user-chosen display name (from
    ``hermes auth add nous --label <name>``).  It gets embedded in the
    singleton state so that ``_seed_from_singletons`` uses it as the pool
    entry's label on every subsequent ``load_pool("nous")`` instead of the
    auto-derived token fingerprint.  When ``None``, the auto-derived label
    via ``label_from_token`` is used (unchanged default behaviour).

    Returns the upserted :class:`PooledCredential` entry (or ``None`` if
    seeding somehow produced no match — shouldn't happen).
    r   r   r%  rM   Nc              3  :   K   | ]}|j         t          k    |V  d S rT  )r  rw  )r   r  s     rL   r   z+persist_nous_credentials.<locals>.<genexpr>2  s/      JJqah2I&I&I&I&I&I&IJJrK   )r   r   rJ   r5   r   r  r4  r;  r  rc  nextr?  )re  r%  r   r>  r=  r	  s         rL   persist_nous_credentialsr    s7   F 0/////KKE ,U!!## ,U))++g			 % %%''
Z777$$$% % % % % % % % % % % % % % % U###9VDJJDLLNNJJJ  s   +/B&&B*-B*c                     	 ddl m}   | d           dS # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)zGBest-effort pool reseed after providers.nous changes; never fail login.r   r   rM   z7Failed to sync Nous credential pool from auth store: %sN)r   r   r   r   r*  )r   r  s     rL   _sync_nous_pool_from_auth_storer  7  s{    U333333	& U U UNPSTTTTTTTTTUs    
AA  A)r  r  r  rh  c                *   !" t          j                    j        dd         t                      5  t	                      t          d          \   " st          ddd          t                     d!t           	                    d                    p.t          j        d	          pt          j        d
          pt                              d          }t                      }|r|                    d          }nLt          |          }|j        r6|j        t"          vr(t$                              d||j                   t          }t)          t           	                    d                              pt*          }t-                      p|}t/           	                    d          pt0                    }	dE !"fd}
t3          ||           }t5          j        | r| nd          }t9          dt;           	                    d                               t5          j        |ddi|          5 } 	                    d          } 	                    d          }t?          |t.                    r|st          ddd          tA          | 	                    d           	                    d                     }|s|,tC          tE          | d!z   tF                    "          5  tI                     rm 	                    d          } 	                    d          }tA          | 	                    d           	                    d                     } |
d#           |s|mt?          |t.                    r|s|pd$}t          d%| d&d|d'          |rd$n|pd(}t9          d)|t;          |          *           	 tK          |||	|+          }nQ# t          $ rD}tM          |          r/tO           |d,-           tQ          |d,-            |
d.            d}~ww xY wtS          j*        tV          j,                  }t[          |	                    d/                    }|}|d          d<   |	                    d          p| d<   |	                    d0          p 	                    d0          pd1 d0<   |	                    d          p 	                    d           d<   t)          |	                    d                    }|pt*          }t-                      p|}|.                                 d2<   | d/<   tS          j/        |0                                |z   tV          j,        3          .                                 d<    d         } d         }t9          d4|t;          |          t;          |          5            |
d6           ddd           n# 1 swxY w Y   tc           |7           te           |8           | d<   | d<   |	 d<   |du t?          |t.                    r|ndd9 d:<   ddd           n# 1 swxY w Y    |
d;           ddd           n# 1 swxY w Y   !rtg                        	                    d<          }t?          |t.                    r|st          d=dd>?           	                    d@          }ti          |          }|1tE          dAtk          |tm          j6                    z
                      n!t[           	                    dB                    }d|| 	                    dC          ||tn          tn          t/          "ptq                                dD	S )FaX  
    Resolve Nous inference credentials for runtime use.

    Ensures access_token is a valid inference-scoped JWT, refreshing it when
    needed. Concurrent processes coordinate through the auth store file lock.

    Returns dict with: provider, base_url, api_key, key_id, expires_at,
    expires_in, source ("invoke_jwt"), and auth_path.
    Nrz  rM   r  Tr  Fr:   r  r  rC  r  r;   r<   r  r5   r   rR  c                   t                    t                    k    rt          d|            d S 	 t          d           n8# t          $ r+}t          d| t	          |          j                    d }~ww xY wt          d| t                              d                    t                              d                    	           t                    d
t                     d S )Nnous_state_persist_skipped)r  r  rM   nous_state_persist_failed)r  r  r  nous_state_persistedrM  r   )r  r  r^  r  T)
r3  r  r#  r   r  rE   r{  r6  rJ   rc  )r  r  r=  persisted_stater  r>  state_persistedr  s     rL   _persist_statez8resolve_nous_runtime_credentials.<locals>._persist_state  s5   
 /u551/BBC C 0 +!   
 	.z65J[\\\\   / +!#Cyy1	     &'!3EIIo4N4N!O!O 2599^3L3L M M    #5kkO"O
 %U+++++s   A
 

A?&A::A?r  r$   nous_runtime_credentials_startrM  )r  r^  rQ  r  r  r   r  r=   r  r  r  r  !post_shared_merge_access_unusablerh  r  r  rL  access_unusablerefresh_start)r  r  r^  r  runtime_access_refresh_failurerg  'terminal_runtime_access_refresh_failurer  rZ  r[  r!  r  refresh_success)r  r  previous_refresh_token_fpnew_refresh_token_fpr  r  r  r|  r  &resolve_nous_runtime_credentials_finalr$  z*Failed to resolve a Nous inference API keyserver_errorr:  r&  r   r'  r%  )	rM  r$  rQ   key_idr  r  r  r>  
state_pathr  r5   r   rR  )9r  r  r  r  r4  r  rK  rJ   r  r6  r  r  r  rE  r  r   r  r  r   r   r  ra  r  r5   r`  r(  r'  r  r  r{  r  r   r  rP  r  r  rZ  r  rn  rt  rz  r   r  r   r  r  r  r  r  r  r-  r  r  r   r  NOUS_AUTH_PATH_INVOKE_JWTr  )#r  r  r  rh  r:   r  r  stored_inference_base_urlr;   r<   r  r  r  r  r   rM  invoke_jwt_statusr  refresh_reasonrc  r  r  r  previous_refresh_tokenr  rQ   r  r  r  r=  r  r  r>  r  r  s#                                @@@@@@rL    resolve_nous_runtime_credentialsr  A  s	     *,,"3B3'K			 bA bA%''
#CJPV#W#W   	DD%+dD D D D u++ uyy):;;<< 'y122'y/00' '
&++ 	 899 		:188==OO ( 9 9 ) :.?.HPj.j.ji#%6%?   #: 6"599-A#B#BCC  * *	 	" )**G.G 	 		+..H2HII	%	, %	, %	, %	, %	, %	, %	, %	, %	, %	, %	,N !(iTYZZZ-? LMM,#/		/0J0JKK	
 	
 	
 	
 \'H>P3QZ`aaa w	ek 99^44L!IIo66MlC00 H H N)/$H H H H !8ii(( 99\22! ! !
  TD 1 =,SSVAVXq=r=rsss SD SD5e<< L',yy'@'@(-		/(B(B,C("'))G"4"4',yy'>'>- - -)
 ''JKKK$ HD(9(E)-== 	] 	%6%I/F"+!M$*!M !M !M *0%+15# # #  =J)wPaPvev$+(3#1-?-N-N	   "(='-*3=) ) )II  ) " " ">sCC Z <$)$'+K!" !" !" !"
 !>$.$'+K!" !" !" !"
 !//X Y Y Y!" 'l8<88%8|9T9T%U%U
1>.09.0In-1:1O1O1`S`o..7mmL.I.I.pUYYWcMdMd.phpl+)2w)?)?)U599WCUCUg )RR[R_R_`tRuRu(v(v4A4_E_18::W>W + 03}}m,.8l+.6.DMMOOj8X\/ / /#)++ l+ (-^'<(-o(>$-(3#16HI_6`6`1CM1R1R    ''BCCCgSD SD SD SD SD SD SD SD SD SD SD SD SD SD SDj .)    $)'    (7E#$*CE&'!*E+"eO'1&#'>'>HVVD E%Liw	 w	 w	 w	 w	 w	 w	 w	 w	 w	 w	 w	 w	 w	 w	r 	?@@@EbA bA bA bA bA bA bA bA bA bA bA bA bA bA bAH  *')))ii$$Ggs## >7 >D!'n> > > 	> 122J(44M $ 	As=49;;.//000 +A!B!BCC  &))N++  + /+@/@/@AA  s   HY?B7Y8CW3O)(W3)
P7	3?P2	2P7	7F0W3'Y3W77Y:W7;AYY?Y!	!Y?$Y!	%Y??ZZc            	         dd d d d ddd dS )NF)r  r:   r;   access_expires_atr&  r  inference_credential_presentcredential_sourcerA   rA   rK   rL   _empty_nous_auth_statusr  [  s)    "! $"(-!	 	 	rK   c                    	 ddl m}   | d          }|r|                                st                      S t	          |                                          }|st                      S dd}t          ||	          }t          |d
d          }|st                      S t          |dd          }t          t          |dd          pd          	                                
                                }t          |dd          }t          |          o#|                    d          pt          |          }	t          |dd          }
d}|	rt          |dd          pt          }|	|t          |dd          p!t          |dd          pt          |dd          |	r|ndt          |dd          t          |dd          t          |          dd|
 d|
 d
S # t          $ r t                      cY S w xY w)zBest-effort status from the credential pool.

    This is a fallback only. The auth-store provider state is the runtime source
    of truth because it is what ``resolve_nous_runtime_credentials()`` refreshes.
    r   r   rM   r
  r   r   tuple[float, float, int]c                    t          t          | dd                     pd}t          t          | dd                     pd}t          t          | dd          pd          }||| fS )Nr&  rR  r  priorityr   )r  r  r   )r
  	agent_exp
access_expr  s       rL   _entry_sort_keyz3_snapshot_nous_pool_status.<locals>._entry_sort_keyy  so    ,WU<RTX-Y-YZZa^aI-ge\4.P.PQQXUXJ75*a88=A>>HzH955rK   r  r   Nr   r8   r9   rM  oauthr%  r,   r:   r;   runtime_base_urlr$  r  r&  Tpool:)
r  r:   r;   r   r  r&  r  r  r  r  )r
  r   r   r  )r   r   r   r  r4  r?  r  r  r5   r   r   r   r   r  r   )r   r	  r?  r  r
  runtime_keyr   r8   rM  is_portal_oauthr%  portal_status_urls               rL   _snapshot_nous_pool_statusr  h  sY   2)333333y   	-4//11 	-*,,,t||~~&& 	-*,,,	6 	6 	6 	6 G111e%6== 	-*,,,und;;{B77=2>>DDFFLLNN	==|,, 
  ))@T--@-@ 	 w	22  	0$77 +*  )0")%1Et"L"L #0u0$77#0uj$//,;ELL!(d!C!C$+E3I4$P$P!%m!4!4,0!0%eoo
 
 	
  ) ) )&((((()s#   4G 0G (5G D8G G21G2z<Optional[Tuple[float, str, Optional[float], Dict[str, Any]]]_nous_auth_status_cacheTuple[str, Optional[float]]c                 $   t                      } 	 t          |                     d                    }n# t          $ r t          |           }Y nw xY w	 ||                                 j        fS # t          $ r |d fcY S t          $ r |d fcY S w xY w)NFr  )r  r5   r  r   r  st_mtimerh  )r  auth_file_keys     rL   _auth_file_cache_keyr    s    !!I'I--U-;;<< ' ' 'I'#inn..777 # # #d"""" # # #d""""#s'   #4 AAA/ /B?BBc                 
    da dS )u`  Clear the get_nous_auth_status() process-level memo.

    Call this from any code path that mutates Nous auth state without going
    through resolve_nous_runtime_credentials() (e.g. tests). Login/logout
    flows touch auth.json, so the mtime check below invalidates them
    automatically — explicit invalidation is the belt-and-braces option.
    N)r  rA   rK   rL   rs  rs    s     #rK   c                    t          j                    } t                      \  }}t          }|0|\  }}}}||k    r#||k    r| |z
  t          k     rt          |          S t                      }| ||t          |          fa|S )a  Status snapshot for Nous auth.

    Prefer the auth-store provider state, because that is the live source of
    truth for refresh operations. When provider state exists, validate it
    by resolving runtime credentials so revoked refresh sessions do not show up
    as a healthy login. If provider state is absent, fall back to the credential
    pool for the just-logged-in / not-yet-promoted case.

    The returned snapshot is memoised for ~15s keyed on the auth.json mtime,
    so menu/status surfaces that ask repeatedly don't trigger one refresh POST
    per call. Login/logout flows write to auth.json and therefore invalidate
    the cache automatically; tests can also call
    ``invalidate_nous_auth_status_cache()`` explicitly.
    )r  r  r  r  _NOUS_AUTH_STATUS_CACHE_TTLrJ   _compute_nous_auth_status)	r  r  mtimer?  	cached_atcached_auth_file_keycached_mtimecached_statusstatuss	            rL   get_nous_auth_statusr     s      .

C/11M5$FGMD	'} M11%%y$???&&&&((F"M5$v,,GMrK   c                    t          d          } | rt          |                     d                    |                     d          |                     d          |                     d          |                     d          t          |                     d                    |                     d          t          |                     d          p|                     d                    d	d	d

}	 t                      }t          d          p| }|                    d|                    d          p|                    d          |                    d          p)|                    d          p|                    d          |                    d          p|                    d          |                    d          p)|                    d          p|                    d          t          |                    d                    dd	d|                    dd           |                    d          d
           |S # t
          $ r^}|                    dt          |          t          t          |dd                    t          |dd          d           |cY d}~S d}~ww xY wt                      S )zEUncached implementation of get_nous_auth_status(). See that function.rM   r   r:   r;   r  r&  rM  r$  r=  )
r  r:   r;   r  r&  r  r   r  r  r  Tr$  r  zruntime:r  portalr  )
r  r:   r;   r  r&  r  r  r  r  r  FrO  rN  N)r  rZ  rO  rB  )	r^  r   r6  r  r  rK  r5   r  r  )r>  base_statusre  refreshed_stater  s        rL   r  r    s   #F++E ,eii7788$yy):;;"')),@"A"A!&<!8!8$)II.D$E$E!%eii&@&@!A!A!IIn55,0		.))CUYY{-C-C- - "."
 
	466E5f==FO!%'6':':;L'M'M'sQ\Q`Q`arQsQs*/))J*?*? +=&**+?@@+="';<<)8)<)<\)J)J)rkoo^qNrNr,1IIl,C,C -?&**+ABB-?"'=>>)-o.A.A/.R.R)S)S48)5H8X)F)FHH#ii11   $  	 	 	"S$(6H%)P)P$Q$Q%c6488	       	 &'''s    5EI
 

J2AJ-'J2-J2c                 ,   	 ddl m}   | d          }|r/|                                r|                                }|wt	          |dd          pt	          |dd          }|rSt          |d          sCdt          t                                t	          |d	d          d
dt	          |dd           |dS t                      }|r}dt          t                                |	                    d	          d
d|	                    d          pd dt          |	                    d          pd|	                    d          d	S n# t          $ r Y nw xY w	 t                      }dt          t                                |	                    d	          |	                    d          |	                    d          |	                    d          dS # t          $ r6}dt          t                                t          |          dcY d}~S d}~ww xY w)zStatus snapshot for Codex auth.
    
    Checks the credential pool first (where `hermes auth` stores credentials),
    then falls back to the legacy provider state.
    r   r   rO   Nr   r   r9   Tre  ru  r  r%  r,   r  r=  re  rv  r  rQ   rP  zCCodex provider quota exhausted; retry after the usage limit resets.r  )	r  r=  re  rv  r  rate_limitedrB  rZ  r  rv  r  rQ   Fr  r=  rZ  )r   r   r   selectr  r5  r5   r  r  r6  r\  r   r  rK  )r   r	  r
  rQ   r  re  r  s          rL   get_codex_auth_statusr
    su   $333333y(( 	D((** 	KKMME E#4d;; :unb99   #B7A#N#N %)&)/*;*;&<&<(/~t(L(L%."N'%)*L*L"N"N#*   788J !%"%o&7&7"8"8$.NN>$B$B!*LjnnW&=&=&JLL$("9"y11 a` *z : :      
133o//00!IIn55;//ii))yy++
 
 	
  
 
 
o//00XX
 
 	
 	
 	
 	
 	
 	

s8   B5E 8BE 
EEA;G 
H+HHHc            
        	 ddl m}   | d          }|r|                                r|                                }|wt	          |dd           pt	          |dd          }|rSt          |d          sCdt          t                                t	          |dd           d	d
t	          |dd           |dS n# t          $ r Y nw xY w	 t                      }dt          t                                |
                    d          |
                    d          |
                    d          |
                    d          dS # t          $ r6}dt          t                                t          |          dcY d }~S d }~ww xY w)Nr   r   rT   r   r   r9   Tre  rN   r  r%  r,   r  rv  r  rQ   Fr  )r   r   r   r	  r  r  r5   r  r   r  r6  rK  )r   r	  r
  rQ   re  r  s         rL   get_xai_oauth_auth_statusr  `  s   333333y%% 	D((** 	KKMME E#4d;; :unb99   #@!#L#L %)&)/*;*;&<&<(/~t(L(L &9"N'%)*L*L"N"N#*
 
 
    
577o//00!IIn55;//ii))yy++
 
 	
  
 
 
o//00XX
 
 	
 	
 	
 	
 	
 	

s1   B3B7 7
CCA;E 
F+E?9F?Fc                   t                               |           }|r|j        dk    rddiS d}d}t          | |          \  }}d}|j        r,t          j        |j        d                                          }| dv rt          ||j	        |          }n|r|}n|j	        }t          |          | |j        ||t          |          dS )z<Status snapshot for API-key providers (z.ai, Kimi, MiniMax).rQ   
configuredFr9      rj   rn   )r  rM  r7   
key_sourcer$  r  )r   r6  r8   r  rD   r  r  r   r   r;   r   r7   )r   r   rQ   r  env_urlr$  s         rL   get_api_key_provider_statusr    s    ##K00G %g'944e$$GJ:;PPGZG B)G4b99??AA777)'73MwWW	 .- 7mm ']]  rK   c                   t                               |           }|r|j        dk    rddiS t          j        dd                                          p(t          j        dd                                          pd}t          j        dd                                          }|rt          j        |          nd	d
g}|j        r,t          j        |j        d                                          nd}|s|j	        }|rt          j        |          nd}t          |p|                    d                    | |j        ||||t          |p|                    d                    dS )z:Status snapshot for providers that run a local subprocess.r_   r  FHERMES_COPILOT_ACP_COMMANDr9   COPILOT_CLI_PATHrY   HERMES_COPILOT_ACP_ARGS--acp--stdioN
acp+tcp://)r  rM  r7   commandr  resolved_commandr$  r  )r   r6  r8   r  r  r   shlexr  rD   r;   r  whichr   r   r7   )r   r   r  raw_argsr  r$  r  s          rL   $get_external_process_provider_statusr    sy   ##K00G %g'+===e$$ 		.3399;; 	9',,2244	 
 y2B77==??H$,F5;x   7I2FDBIBZbry1266<<>>>`bH .-07Av|G,,,T+Px/B/B</P/PQQ,*Oh.A.A,.O.OPP	 	 	rK   c                   | pt                      pd                                                                }|sddiS |dk    rt                      S |dk    rt	                      S |dk    rt                      S |dk    rt                      S |dk    rt                      S |d	k    rt                      S |d
k    rt          |          S |dk    rt                      S t                              |          }|r|j        dk    rt          |          S |r5|j        dk    r*	 ddlm}  |            |dS # t"          $ r	 d|ddcY S w xY wddiS )zGeneric auth status dispatcher.r9   r  Fr/   rM   rO   rT   rU   r}   r^   r   rQ   r   r   r  r  rM  zboto3 not installed)r  rM  rZ  )ra  r   r   r  r   r
  r  rs  get_minimax_oauth_auth_statusr  _get_azure_foundry_auth_statusr   r6  r8   r  r  r  r  )r   rl  r   r  s       rL   r  r    s   80228b??AAGGIIF $U##&(((#%%%$&&&(***#%%%  ,...3F;;;  -///##F++G 37$	11*6222 \7$	11	\AAAAAA!4!4!6!6FKKK 	\ 	\ 	\!&FEZ[[[[[	\s   "D5 5EEc                 6   ddi} 	 ddl m}m}  |            }n# t          $ r i }Y nw xY wt	          |t
                    r|                    d          nd}d}d}t	          |t
                    rt          |                    d	          pd                                          	                                pd}t          |                    d
          pd                                          }|| d	<   || d
<   |dk    r	 ddl
m}m}m}	  |	            }
i }t	          |t
                    r0t	          |                    d          t
                    r|d         }|                    ||          }|
| d<   |j        | d<   d| d<   d| d<   t!          |
          | d<   |
sd| d<   nd| d<   | S # t          $ r}d| d<   d| | d<   | cY d}~S d}~ww xY w	  |d          pd}n%# t          $ r t#          j        dd          }Y nw xY wt'          |          | d<   | S )uW  Return structural auth status for Azure Foundry.

    ``logged_in`` is structural, matching other non-OAuth provider status
    checks:

      * ``auth_mode == "entra_id"`` AND ``azure-identity`` is importable
        (we do NOT mint a token here; ``hermes doctor`` runs the live
        probe and reports whether the credential chain can acquire one).
      * ``auth_mode == "api_key"`` (default) AND ``AZURE_FOUNDRY_API_KEY``
        is set with a usable value.

    Never invokes the Entra credential chain — keeps CLI startup latency
    flat regardless of token-service / az login state.
    rM  r   r   )rd  r   r  NrQ   r9   rv  r$  entra_id)EntraIdentityConfigSCOPE_AI_AZURE_DEFAULThas_azure_identity_installedentra)default_scopeazure_identity_installedr=   not_runcredential_probeFcredential_verifiedr  zwazure-identity not installed. Install with: pip install azure-identity  (or rely on Hermes' lazy-install at first use).rw  zyazure-identity is installed; live credential validation is skipped here. Run `hermes doctor` to verify token acquisition.zazure-identity check failed: rZ  r   )r   rd  r   r   r   rJ   r6  r5   r   r   agent.azure_identity_adapterr&  r'  r(  	from_dictr=   r   r  r  r   )r<  rd  r   rf  rg  rv  r$  r&  r'  r(  	installed	entra_cfgidentity_configr  rQ   s                  rL   r#  r#    s    '8DNNNNNNNNkmm    %/sD$9$9C   tIIH)T"" @	k22?i@@FFHHNNPP]T]	y}}Z006B77==??!DDJ"	         
 5466II)T** /z)--:P:PRV/W/W /%g.	1;;4 <  O 09D+,+1DM'0D#$*/D&' $YD 
2 VX V K 	 	 	 %DACAADMKKKKKK	9--.EFFL" 9 9 9)3R889)'22DKs=    ((B+F- -
G7GGGG" "HHc                   t                               |           }|r|j        dk    rt          d|  d| d          d}d}t	          | |          \  }}|s| dk    rt
          }|pd}d}|j        r,t          j        |j        d          	                                }| d	v rt          ||j        |          }n| d
k    rt          ||j        |          }n| dk    r|r|                    d          n|j        }	 ddlm}m}  |            \  }}	|r( ||          \  }	}
|
pd	                                }
|
r|
}nQ# t"          $ r%}t$                              d|           Y d}~n'd}~ww xY w|r|                    d          }n|j        }| dk    rt)          |          }t+          |t,                    r|	                                s|j        }| ||                    d          |pddS )zwResolve API key and base URL for an API-key provider.

    Returns dict with: provider, api_key, base_url, source.
    rQ   
Provider 'z' is not an API-key provider.r  r:  r9   rV   r  r  re   rY   rC  r   r   z4Copilot base URL resolution fell back to default: %sNrM  rQ   r$  r  )r   r6  r8   rK  r  LMSTUDIO_NOAUTH_PLACEHOLDERrD   r  r  r   r   r;   rA  rE  r   r   r   r   r   r*  rH  r   r5   )r   r   rQ   r  r  r$  r   r   	raw_token_r  r  s               rL   $resolve_api_key_provider_credentialsr:  :  s   
  ##K00G 
g'944CCCC #
 
 
 	
 GJ:;PPGZ
  -{j00-,9
G B)G4b99??AA777)'73MwWW			('2LgVV			!	! +2Q7>>#&&&w7Q	V        1022LIq (33I>>8$N1133 ('H 	V 	V 	VLLOQTUUUUUUUU	V	 .>>#&&-j  7AA
 x%% .(..*:*: .-  OOC(()		  s   :?D: :
E)E$$E)c                   t                               |           }|r|j        dk    rt          d|  d| d          |j        r,t          j        |j        d                                          nd}|s|j        }t          j        dd                                          p(t          j        dd                                          pd	}t          j        d
d                                          }|rt          j
        |          nddg}|rt          j        |          nd}|s+|                    d          st          d| d| d          | d|                    d          |p||ddS )z>Resolve runtime details for local subprocess-backed providers.r_   r5  z&' is not an external-process provider.r  r:  r9   r  r  rY   r  r  r  Nr  z(Could not find the Copilot CLI command 'zQ'. Install GitHub Copilot CLI or set HERMES_COPILOT_ACP_COMMAND/COPILOT_CLI_PATH.missing_copilot_clir^   rC  process)rM  rQ   r$  r  r  r  )r   r6  r8   rK  rD   r  r  r   r;   r  r  r  r  r   rE  )r   r   r$  r  r  r  r  s          rL   -resolve_external_process_provider_credentialsr>    s   ##K00G 
g'+===LLLL #
 
 
 	
 CJBZbry1266<<>>>`bH .- 		.3399;; 	9',,2244	 
 y2B77==??H$,F5;x   7I2FD07Av|G,,,T 
H$7$7$E$E 
]w ] ] ] &	
 
 
 	
   OOC((#.w  rK   default_modelc                F   t                      5  t                      }| |d<   t          |           ddd           n# 1 swxY w Y   t                      }|j                            dd           t          |           t                      }|                    d          }t          |t                    rt          |          }nBt          |t                    r+|                                rd|                                i}ni }| |d<   |r-|                                r|                    d          |d	<   n|                    d	d           d
dlm}  ||           |r!|                    dd          }	|	rd|	v r||d<   ||d<   t#          ||d           |S )a  Update config.yaml and auth.json to reflect the active provider.

    When *default_model* is provided the function also writes it as the
    ``model.default`` value.  This prevents a race condition where the
    gateway (which re-reads config per-message) picks up the new provider
    before the caller has finished model selection, resulting in a
    mismatched model/provider (e.g. ``anthropic/claude-opus-4.6`` sent to
    MiniMax's API).
    r  NTr  r  r  rM  rC  r$  r   ) clear_model_endpoint_credentialsr9   Fr  )r  r4  r  r   r  r  r   r   r6  r   rJ   r5   r   rE  r[  r   rA  r    )
r   r;   r?  r=  config_pathconfigcurrent_modelrg  rA  cur_defaults
             rL   _update_config_for_providerrG    s    
		 % %%''
(3
$%$$$% % % % % % % % % % % % % % % "##KTD999(555FJJw''M-&& ''			M3	'	' M,?,?,A,A  3 3 5 56			'Ij (06688 ( 2 9 9# > >	* 	j$'''
 CBBBBB$$Y///
  1mmIr22 	1c[00#0Ii F7Ok6U;;;;s   #>AAc                 P   	 t                      } n# t          $ r Y dS w xY w| sdS |                     d          }t          |t                    sdS |                    d          }t          |t
                    sdS |                                                                }|pdS )z?Return model.provider from config.yaml, normalized, if present.Nr  rM  )r   r   r6  r   rJ   r5   r   r   )rD  r  rM  s      rL   _get_config_providerrI    s     ""   tt tJJwEeT"" tyy$$Hh$$ t~~%%''Hts    
c                v    | sdS t                      |                                                                 k    S )z=Return True when config.yaml currently selects *provider_id*.F)rI  r   r   )r   s    rL   _config_provider_matchesrK    s8     u!![%6%6%8%8%>%>%@%@@@rK   c                    | sdS |                                                                  }|t          v ot          |          S )z?Return True when logout should reset the model provider config.F)r   r   r   rK  r-  s     rL   '_should_reset_config_provider_on_logoutrM    sF     u""$$**,,J**S/G
/S/SSrK   c                 .    t                      } | dv r| S dS )a  Fallback logout target when auth.json has no active provider.

    `hermes logout` historically keyed off auth.json.active_provider only.
    That left users stuck when auth state had already been cleared but
    config.yaml still selected an OAuth provider such as openai-codex for the
    agent model: there was no active auth provider to target, so logout printed
    "No provider is currently logged in" and never reset model.provider.
    >   rM   rT   rO   N)rI  )rM  s    rL   $_logout_default_provider_from_configrO    s%     $%%H8884rK   c                 .   t                      } |                                 s| S t          |            t                      }|s| S |                    d          }t          |t                    rd|d<   d|v r
t          |d<   t          | |d           | S )z5Reset config.yaml provider back to auto after logout.r  r  rM  r$  FrB  )	r   r  r   r   r6  r   rJ   r   r    )rC  rD  r  s      rL   _reset_config_providerrQ    s    !##K (555F JJwE% 4"j 3E*k6U;;;;rK   rM  r$  rQ   r  rM  c                  	 ddl m}  || |||          }n# t          $ r d}Y nw xY w|dS t                       t          d           t          |j                   t          d           	 t          d                                                                          }n&# t          t          f$ r t                       Y dS w xY w|d	v S )
zDPrompt before saving a model whose known pricing exceeds guardrails.r   )expensive_model_warningrR  NTzH========================================================================zSwitch anyway? [y/N]: F>   yr  )
hermes_cli.model_cost_guardrT  r   r  rP  r  r   r   r  r  )r  rM  r$  rQ   rT  r   r`  s          rL   "_confirm_expensive_model_selectionrW  3  s   
GGGGGG))	
 
 
    t	GGG	(OOO	'/	(OOO12288::@@BBx(   uu |##s    ''/3B# #CCr  rE  pricing#Optional[Dict[str, Dict[str, str]]]unavailable_modelsOptional[List[str]]
portal_urlunavailable_messageconfirm_providerconfirm_base_urlconfirm_api_keyc	           
     v  '()*+,- ddl m}	 |pg }
d;fd}g }r| v r|                               | D ]}||vr|                    |           t          |          t          |
          z   }t	          ot          fd|D                                 ++rt          d	 |D             d
          dz   nd,i (d-d)d*+r|D ]}                    |          }|rh |	|                    dd                    } |	|                    dd                    }|                    dd          }|r |	|          nd}|rd*nd\  }}}|||f(|<   t          -t          |          t          |                    -t          )t          |                    )؉*rt          )d          )()*+,-fd'd}d}+r1d}d| dd, ddd- ddd- }*r|ddd) z  }||dz   z  }d }d!}	 dd"l	m
} 'fd#|D             }|                    d$           |                    d%           |pt                              d&          }|                                }|s|
rd'| d(}g }+rV|                    dd)          }t          |          d)k    r-|                    |d)                                                    |
r?|
D ]#}|                    d* '|                      $|                    d+| d,           |rd                    |          nd-}  |d||d.| d/          }!|!dk     rd-S t%                       |!t          |          k     r |||!                   S |!t          |          k    rJ	 t'          d0                                          }"n# t(          t*          f$ r Y d-S w xY w|"r ||"          nd-S d-S # t,          t.          t0          t2          j        f$ r Y nw xY wt%          |           t          t7          t          |          dz                       }#t9          |d)          D ]'\  }$}t%          d|$d|# d1 '|                      (t          |          }%t%          d|%d)z   d|# d2           t%          d|%dz   d|# d3           |
r|pt                              d&          }|                                pd4| d5}t%                       t%          d| d6| d,|            |
D ](}t%          ddd|# d|  '|           |            )t%                       	 	 t'          d7|%dz    d8                                          }&|&sd-S t;          |&          }!d)|!cxk    r|%k    rn n |||!d)z
                     S |!|%d)z   k    r0t'          d0                                          }"|"r ||"          nd-S |!|%dz   k    rd-S t%          d9|%dz               n2# t<          $ r t%          d:           Y nt*          t(          f$ r Y d-S w xY w)<a  Interactive model selection. Puts current_model first with a marker. Returns chosen model ID or None.

    If *pricing* is provided (``{model_id: {prompt, completion}}``), a compact
    price indicator is shown next to each model in aligned columns.

    If *unavailable_models* is provided, those models are shown grayed out
    and unselectable, with an upgrade link to *portal_url*.
    r   )_format_price_per_mtokr  r5   r   rQ  c                >    | sd S rt          |           sd S | S )NrR  )rW  )r  r`  r_  r^  s    rL   _confirmed_selectionz5_prompt_model_selection.<locals>._confirmed_selectionl  sG     	4 	$F%%#	%
 %
 %
 	 4
rK   c              3  B   K   | ]}                     |          V  d S rT  r6  )r   mrX  s     rL   r   z*_prompt_model_selection.<locals>.<genexpr>  s-      &J&J!w{{1~~&J&J&J&J&J&JrK   c              3  4   K   | ]}t          |          V  d S rT  )r   )r   rg  s     rL   r   z*_prompt_model_selection.<locals>.<genexpr>  s(      //qCFF//////rK   r  r  r  Fpromptr9   
completioninput_cache_readTr9   r9   r9      c                    
rC                     | d          \  }}}d|d d|d }	r|d|d z  }| d | }n| }| k    r|dz  }|S )Nrl  r.   >  <u     ← currently in userf  )r  inpoutcache
price_partrC  _price_cache	cache_colrE  	has_cachehas_pricingname_col	price_cols         rL   _labelz'_prompt_model_selection.<locals>._label  s     	*..sLAAOCeCSC9CCCCCyCCCCJ 8757977777
3H333z33DDD-,,DrK   zSelect default model:z     r  ro  r.   Inrp  OutCachez  /Mtokz[2mz[0m)curses_radiolistc                &    g | ]} |          S rA   rA   )r   r  r|  s     rL   rG  z+_prompt_model_selection.<locals>.<listcomp>  s!    222366#;;222rK   zEnter custom model namezSkip (keep current)rC  Upgrade at z for paid modelsr#   z   u	     ── u    ──Nr  )selectedcancel_returnsrC  
searchablezEnter model name: z. z. Enter custom model namez. Skip (keep current)u6   Unavailable models (requires paid tier — upgrade at )u   ── z
Choice [1-z] (default: skip): zPlease enter 1-zPlease enter a number)r  r5   r   rQ  )hermes_cli.modelsrb  rI  r4  r   anyr  r6  r   hermes_cli.curses_uir  r  rE  r   r  extendrx  ry  r  r  r  r  r  NotImplementedErrorr  
subprocessSubprocessErrorr5   	enumerater   r   ).r  rE  rX  rZ  r\  r]  r^  r_  r`  rb  _unavailablerd  rz  r  
all_modelsprr  rs  
cache_readrt  default_idx
menu_titlepadheader_DIM_RESETr  choices_upgrade_urlunavailable_footer
desc_linesheader_partrC  idxr   	num_widthinchoicer|  rv  rw  rx  ry  rz  r{  s.    ``   ```                              @@@@@@@rL   _prompt_model_selectionr  U  s   & 988888%+L
 
 
 
 
 
 
 
 G &)33}%%%    gNN3 gl!3!33J wJ3&J&J&J&Jz&J&J&J#J#JKKKCNUs//J///;;;a??TUH 57LIII * 	3 	3CC  A -,,QUU8R-@-@AA,,QUU<-D-DEEUU#5r::
>HP..z:::b % $I",S%!$c5 1LIs3xxS::IIs5zz22II 	*Iq))I           K )J ) UcU2UUUUUDU9UUUUUUUUU 	2171Y11111Ffy((
 DF19999992222'2220111,---"=&=EEcJJ06688! 	Nl 	N!M|!M!M!M
 !#
 	? %**433K;!##!!+a.";";"="=>>> 	G# 7 7!!"5s"5"56666E*<EEEFFF/9Cdii
+++t# #
 
 
 774W''555CLL  344::<</0   tt39C''///tCt,gz7QR    
*CGq())**IGQ'' 4 43212y2222VVC[[223333GA	
<q1u
<y
<
<
<
<
<===	
8q1u
8y
8
8
8
8
8999 G"=&=EEcJJ06688 
T\TTT 	 	C4CC 2CC6CCDDD 	G 	GCErEIEEEE$EsEVEEFFFF	GGG	BABBBCCIIKKF tf++CC}}}}1}}}}}++GC!G,<===A344::<<7=G++F3334GAt+AE++,,,, 	+ 	+ 	+)*****!8, 	 	 	44	!ss   EO 1O 9O !N/ .O /O O OO "O=<O=*X ;2X .8X '	X 2X X7#X76X7c                    ddl m}m}  |            }t          |                    d          t
                    r| |d         d<   nd| i|d<    ||           dS )u   Save the selected model to config.yaml (single source of truth).

    The model is stored in config.yaml only — NOT in .env.  This avoids
    conflicts in multi-agent setups where env vars would stomp each other.
    r   )save_configrd  r  r  N)r   r  rd  r   r6  rJ   )r  r  rd  rD  s       rL   _save_model_choicer    sz     ;:::::::[]]F&**W%%t,, 0%-w	""$h/wKrK   c                z    t          d           t          d           t          d           t          d          )z9Deprecated: use 'hermes model' or 'hermes setup' instead.z,The 'hermes login' command has been removed.z(Use 'hermes auth' to manage credentials,zF'hermes model' to select a provider, or 'hermes setup' for full setup.r   )r  r  )r  s    rL   login_commandr  /  s;    	
8999	
4555	
RSSS
Q--rK   )force_new_loginr  c                  ~ ~|s)	 t                      }|                    dd          }t          |t                    r|rt	          |d          st          d           	 t          d                                                                          }n# t          t          f$ r d}Y nw xY w|dv r[t          d|                    d	t                              }t                       t          d
           t          d| d           dS nt          d           n# t          $ r Y nw xY w|s"t                      }|rt          d           t          d           	 t          d                                                                          }n# t          t          f$ r d}Y nw xY w|dv rt          |           t!          j        dd                                                              d          pt          }	t          d|	          }t                       t          d           t          d           t          d| d           dS t                       t          d           t          d           t                       t'                      }
t          |
d         |
                    d                     t          d|
                    d	t                              }t                       t          d
           ddlm} t          d |             d           t          d| d           dS ) zNOpenAI Codex login via device code flow. Tokens stored in ~/.hermes/auth.json.rQ   r9   r)   z6Existing Codex credentials found in Hermes auth store.!Use existing credentials? [Y/n]: rU  >   r9   rU  r  rO   r$  Login successful!  Config updated: z (model.provider=openai-codex)Nz?Existing Codex credentials are expired. Starting fresh login...z:Found existing Codex CLI credentials at ~/.codex/auth.jsonzOHermes will create its own session to avoid conflicts with Codex CLI / VS Code.zCImport these credentials? (a separate login is recommended) [y/N]: r  >   rU  r  r  rC  z=Credentials imported. Note: if Codex CLI refreshes its token,z<Hermes will keep working independently with its own session.zSigning in to OpenAI Codex...uF   (Hermes creates its own session — won't affect Codex CLI or VS Code)rA  re  r   display_hermes_homer"  
/auth.json)r  r6  r   r5   r5  r  r  r   r   r  r  rG  r  rK  ry  rw  r  r  rE  _codex_device_code_loginr  r  )r  r   r  r:  _resolved_keyr  rC  
cli_tokens	do_importr$  re  _dhhs               rL   _login_openai_codexr  7  s    	g  	8::H
 %LLB77M--- Y- YHghuwyHzHz YNOOO !"EFFLLNNTTVVEE "34      EEE ,,,"=nhll[eg}N~N~""KGGG-...Z{ZZZ[[[F - WXXX 	 	 	D	  -//
 	NOOOcddd !"ghhnnppvvxx		/0      			 L((":...9%<bAAGGIIPPQTUUoYo9.(SSUVVVTUUUV;VVVWWW 
GGG	
)***	
RSSS	GGG$&&E uX		.(A(ABBB-neii
Tj>k>kllK	GGG	
<<<<<<	
-4466
-
-
-...	
J{
J
J
JKKKKKsO   AD "3B D B,)D +B,,A D D 
D,+D,"3F F,+F,c                  ~|s	 t                      }|                    dd          }t          |t                    r|rt	          |d          st          d           	 t          d                                                                          }n# t          t          f$ r d}Y nw xY w|dv r[t          d|                    d	t                              }t                       t          d
           t          d| d           d S n# t          $ r Y nw xY wt                       t          d           t          d           t                       t          t          | dd           pd          }t          | dd           }t!                      rd}t#          ||          }	t%          |	d         |	                    d          |	                    dd          |	                    d          d           t'          dd           t          d|	                    d	t                              }t                       t          d
           ddlm}
 t          d |
             d           t          d| d           d S )NrQ   r9   r)   z:Existing xAI OAuth credentials found in Hermes auth store.r  rU  >   r9   rU  r  rT   r$  r  r  z (model.provider=xai-oauth)z6Signing in to xAI Grok OAuth (SuperGrok / Premium+)...z,(Hermes creates its own local OAuth session)r  rK  r  Fr  r'  rA  r  r  re  rN   r  r&   r   r  r"  r  )r  r6  r   r5   r  r  r  r   r   r  r  rG  r  rK  r  r  r  _xai_oauth_device_code_loginr  r\  r  r  )r  r   r  r:  rQ   r  rC  r  r'  re  r  s              rL   _login_xai_oauthr    s    	 	<>>Hll9b11G'3'' G <YZace<f<f RSSS !"EFFLLNNTTVVEE "34      EEE ,,,"=# Z1KLL# #K GGG-...W{WWWXXXF 	 	 	D	 
GGG	
BCCC	
8999	GGGGD)T::BdCCOt\5999L ('!  E h))K((YY~r22YY~..%    !m<<<-k599ZQk;l;lmmK	GGG	
<<<<<<	
-4466
-
-
-...	
G{
G
G
GHHHHHs7   AD !3B D B+(D *B++A D 
DD)r=   c                  |                      t          dddt          |d          }|j        dk    rAt	          d|j         d|j        rd	|j                                         nd
z   dd          |                                d}fd|D             }|r(t	          dd                    |           dd          S )NrO  r  rP  )r<   r=   r~  r#  z%xAI device-code request failed (HTTP z).rW  r9   rT   device_code_request_failedr:  r,  c                    g | ]}|v|	S rA   rA   )r   r  r  s     rL   rG  z2_xai_oauth_request_device_code.<locals>.<listcomp>  s#    ===s#W*<*<s*<*<*<rK   z)xAI device-code response missing fields: r3  device_code_invalid)	r(  XAI_OAUTH_DEVICE_CODE_URLr  r)  rK  r  r   r"  ry  )r  r=   r`  requiredr6  r  s        @rL   _xai_oauth_request_device_coder    s    
 {{!?(
 

 -
 
  
 
H s""LH4HLLL8@M4X]00224442O -	
 
 
 	
 mmooGH >===h===G 
L		'8J8JLL &
 
 
 	

 NrK   c                  t          j                    t          dt          |                    z   }t          dt          |                    }t          j                    |k     r|                     |ddddt
          |d          }|j        dk    rd|                                }|                    d	          st          d
dd          |                    d          st          ddd          |S 	 |                                }	n4# t          $ r' |                                 t          ddd          w xY wt          |	                    d          pd          }
|
dk    rt          j        |           5|
dk    r)t          |dz   d          }t          j        |           d|	                    d          p|	                    d          p|j        }t          d| dd          t          ddd          )Nr#   rO  r  rP  r:  r;  r~  r#  r   z?xAI device-code token response did not include an access_token.rT   xai_device_token_invalidr:  rM  z?xAI device-code token response did not include a refresh_token.zAxAI device-code token polling returned a non-JSON error response.xai_device_token_failedrZ  r9   r<  r=  r>  r  z&xAI device-code token polling failed: z/Timed out waiting for xAI device authorization.device_code_timeout)r  r  r  r   r(  r  r)  r"  r6  rK  r   r4  r5   r  r  r  )r  r  r&   r  r  r  r@  r`  r  rA  rB  rC  s               rL   _xai_oauth_poll_device_tokenr    s{    ~#aZ"9"99H1c-0011
.

X
%
%;; C, 
 M0*   
 
 3&&mmooG;;~.. U(3   
 ;;// U(3   
 N	$MMOOMM 	 	 	%%'''S$.   	 **7339r::
000J'((($$"#3a#7<<J'(((122   ))} 	
 B[BB *
 
 
 	

 9"   s   ;D 1Er  r'  c           	     Z   t          |           }|d         }t          j        t          d|                     }t          j        |ddi          5 }t          |          }t          |                    d          p|d                   }t          |d                   }t          |d	                   }	t          |d
                   }
t                       t          d           t          d|            t          d|            |ret                      sWt                      rI	 t          j        |          }n# t          $ r d}Y nw xY w|rt          d           nt          d           t          dt          d|
           d           t          ||t          |d                   |	|
          }d d d            n# 1 swxY w Y   t          |                    dd          pd                                          }t          |                    dd          pd                                          }|r|st#          ddd          t%          t'          j        dd                                                              d          p9t'          j        dd                                                              d          t,                     }||t          |                    d!d          pd                                          |                    d	          t          |                    d"          pd#                                          pd#d$|d|t/          j        t2          j                                                                      d%d&          d'd(S ))Nr  rK  rQ  r  r}  r/  r.  r-  r  r0  To continue:  1. Open:   2. If prompted, enter code: F#  (Opened browser for verification)<  Could not open browser automatically -- use the URL above.$Waiting for approval (polling every r#   s)...r&   )r  r&   r  r  r   r9   rM  z;xAI device-code token response was missing required tokens.rT   r  r:  r  rC  r   r  r  rZ  r[  )r   rM  r  r  rZ  r  r  zoauth-device-code)rA  r  r  r$  re  r  )r  r'  r  r  r  r  r5   r6  r   r  r  r$  r  r  r   r  r   rK  r  r  r  rE  r  r   r  r   r  r  r  )r  r'  r  r  r  r  device_dataverification_urlr-  r  r0  r*  r  r   rM  r$  s                   rL   r  r  8  s   
 %_55I/0NmCo6677G	g:L/M	N	N	N 
RX4V<<OO788 /-.
 
 K011	\233
{:.//n.,..///:y::;;; 	V 2 4 4 	V9T9V9V 	V#)9::    V;<<<<TUUULSH5E5ELLLMMM.)K677!"
 
 
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
B w{{>266<"==CCEELOR88>B??EEGGM 
} 
I +
 
 
 	

 0
	',,2244;;C@@ 	=9^R((..0077<<+  H )*GKK
B77=2>>DDFF!++l33gkk,77C8DDJJLLXPX
 
  X\22<<>>FFxQTUU%  s8   CG+E ?G EGEA,GGGc            
        ddl } d}t          }d}d}t          d|dz             D ]}	 t          j        t          j        d                    5 }|                    | dd	|id
di          }ddd           n# 1 swxY w Y   n'# t          $ r}t          d| dd          d}~ww xY w|j	        dk    r n||k     rzt          t          |dd                    }||nd|z  }	t          dt          t          |	          d                    }	t          d|	 d           |                     |	           |M|j	        dk    rBt          t          |dd                    }|d| dnd}
t          d|
 dt"                    ||j	        dk    r!||j	        nd}t          d| ddd          |                                }|                    d d!          }|                    d"d!          }t          d#t          |                    d$d%                              }|r|st          d&dd'          t          d(           t          d)           t          d*| d+           t          d,           t          d*| d-           t          d.           d/}|                                 }d}	 t          j        t          j        d                    5 }|                                 |z
  |k     rz|                     |           |                    | d0||d1d
di          }|j	        dk    r|                                }n%|j	        d2v rzt          d3|j	         ddd4          ddd           n# 1 swxY w Y   n,# t*          $ r t          d5           t-          d6          w xY w|t          d7dd8          |                    d9d!          }|                    d:d!          }| d;}|r|st          d<dd=          	 t          j        t          j        d                    5 }|                    t.          d9||||d>d
d?i@          }ddd           n# 1 swxY w Y   n'# t          $ r}t          dA| ddB          d}~ww xY w|j	        dk    rBt          t          |dd                    }|d| dnd}
t          dC|
 dt"                    |j	        dk    rt          dD|j	         dddE          |                                }|                    dFd!          }|                    dGd!          }|st          dHddI          t1          j        dJd!                                                              dK          pt8          }||dL|t;          j        t>          j                   !                                "                    dMdN          dOdPdQS )RzBRun the OpenAI device code login flow and return credentials dict.r   Nzhttps://auth.openai.comr   r#   r$   r  z!/api/accounts/deviceauth/usercoder<   r  r  )r"  r!  zFailed to request device code: rO   r  r:  r  r!  r  r)   z:OpenAI is rate-limiting login requests (429); retrying in zs...z Try again in about zs.z' Wait a minute and run the login again.zOpenAI is rate-limiting Codex login requests (HTTP 429). This is a temporary throttle on OpenAI's side, not a credential problem.r#  r,   z$Device code request returned status r  device_code_request_errorr-  r9   device_auth_idr  r0  5z-Device code response missing required fields.device_code_incompletez!To continue, follow these steps:
z#  1. Open this URL in your browser:z
     [94mz/codex/device[0m
z  2. Enter this code:z[0m
z/Waiting for sign-in... (press Ctrl+C to cancel)i  z/api/accounts/deviceauth/token)r  r-  >   r  r  z$Device auth polling returned status device_code_poll_error
Login cancelled.   z!Login timed out after 15 minutes.r  r  r  z/deviceauth/callbackzADevice auth response missing authorization_code or code_verifier.device_code_incomplete_exchange)rS  rN  r  r<   r  rO  r?  r!  zToken exchange failed: token_exchange_failedzOpenAI is rate-limiting Codex login requests (HTTP 429) during token exchange. This is a temporary throttle on OpenAI's side, not a credential problem.zToken exchange returned status token_exchange_errorr   rM  z.Token exchange did not return an access_token.token_exchange_no_access_tokenr  rC  )r   rM  r  r  ru  zdevice-code)rA  r$  re  rv  r  )#r  r  ranger'  r  r  r(  r   rK  r)  rd  r  r  r  r   r  r  r\  r"  r6  r  r  r  r  r  r  r   rE  r  r   r  r   r  r  r  )_timeissuerr<   r-  max_attemptsattemptr  r  r  delay	wait_hintr  r  r-  r  r  max_waitr  	code_resp	poll_respr  r  r  
token_resprA  r   rM  r$  s                               rL   r  r  ~  s~   &F%I DLL1,--  	emD&9&9::: f{{@@@%y1+-?@ #                  	 	 	7#77'.J   	 s""E\!!4i.. K
 $/#:KKWE3s5zz2..//E2&+2 2 2   KKD,330y$1O1OPP & 3;2222: 	
 # # # $*A	
 
 
 	
 |t'3..%)%5!!9<6<<<#*E
 
 
 	

 ))++KR00I __%5r::N3{z3??@@AAM 
N 
;#*B
 
 
 	
 

.///	
/000	
8&
8
8
8999	
!"""	
.)
.
.
.///	
;<<< HOOEI\%-"5"5666 	&//##e+h66M***"KK===,:SS+-?@ (  	 (C// ) 0 0I*j88#Wy?TWWW!/6N   	 	 	 	 	 	 	 	 	 	 	 	 	 	 	&    "###oo /#*?
 
 
 	
 #';R@@MM/266M222L 
] 
O#*K
 
 
 	


\%-"5"5666 	&%"6.$0!*%2  ()LM % 
 
J	 	 	 	 	 	 	 	 	 	 	 	 	 	 	  
 
 
+c++#*A
 
 
 	

 $$0J	400
 

 & 3;2222: 	
 4(14 4 $*A	
 
 
 	
 $$Gj.DGGG#*@
 
 
 	

 __F::nb11LJJ33M 
<#*J
 
 
 	
 		)2..4466==cBB 	"!  )*
 
  X\22<<>>FFxQTUU	 	 	s   'B A<0B<B 	 BB 	B
B,B''B,='N $BN:N N

N N
N )N;'Q? &Q3'Q? 3Q77Q? :Q7;Q? ?
R#	RR#rB   c                 V   ddl } |                     d          dd         }t          j        t	          j        |                                                                                                                    	                    d          }|                     d          }|||fS )zGGenerate (code_verifier, code_challenge_S256, state) for MiniMax OAuth.r   Nr  `   r  r2  )
secretstoken_urlsafer  r  r7  r8  r9  r  r  rE  )r  verifier	challenger>  s       rL   _minimax_pkce_pairr  G  s    NNN$$R(("-H(x(())0022 fhhvvc{{  !!"%%EY%%rK   c          
        |                      | dd|t          |d|dddt          t          j                              d          }|j        d	k    r!t          d
|j        p|j         dd          |	                                }dD ]}||vrt          d| dd          |
                    d          |k    rt          ddd          |S )Nz/oauth/coderN  r  )r  r<   r=   r  r  r>  rO  r  )r  rQ  zx-request-idr  r#  z$MiniMax OAuth authorization failed: r}   authorization_failedr:  )r-  r.  
expired_inz&MiniMax OAuth response missing field: authorization_incompleter>  z-MiniMax OAuth state mismatch (possible CSRF).state_mismatch)r(  MINIMAX_OAUTH_SCOPEr5   r  r  r)  rK  r  reason_phraser"  r6  )r  r:   r<   r  r>  r`  r  r   s           rL   _minimax_request_user_coder  R  s@    {{'''#"(,%+
 
 @(
--
 
   H  s""\8=3ZHDZ\\$+A
 
 
 	
 mmooG@  @@@(/I     
 {{7u$$;$+;
 
 
 	
 NrK   r  now_msc               .    t          |           |dz  k    S )zMTrue if ``expired_in`` is plausibly a unix-ms absolute time (vs TTL seconds).r  )r   )r  r  s     rL   &_minimax_expired_in_looks_like_unix_msr  z  s    z??fk**rK   r  r   c                   t          |           }t          |                                dz            }t          ||          r|dz  S |                                t          d|          z   S )zRReturn access-token expiry as unix seconds (MiniMax uses ms epoch or TTL seconds).rH  r  r  r#   )r   r  r  r  )r  r  rb  r  s       rL   "_minimax_resolve_token_expiry_unixr    sa    
j//C4'((F-c&AAA V|==??SC[[((rK   r-  interval_msc                  dd l }t          |                                 dz            }t          |          }	t          |	|          r|	dz  }
n%|                                 t          d|	          z   }
t          d|pddz            }|                                 |
k     r=|                     | dt
          |||d	d
dd          }	 |j        r|                                ni n# t          $ r i Y nw xY w|j	        dk    rI
                    di           pi 
                    d          p|j        }t          d|pd dd          
                    d          }|dk    rt          ddd          |dk    r/t          fddD                       st          ddd          S |                    |           |                                 |
k     =t          ddd           )!Nr   rH  r  r  r#   g       @i  /oauth/token)rS  r<   r-  r  rO  r  rP  r  r#  	base_resp
status_msgzMiniMax OAuth error: r,   r}   r  r:  r  rZ  z8MiniMax OAuth reported an error. Please try again later.authorization_deniedsuccessc              3  B   K   | ]}                     |          V  d S rT  rf  )r   kr  s     rL   r   z&_minimax_poll_token.<locals>.<genexpr>  s-      __!w{{1~~______rK   )r   rM  r  z<MiniMax OAuth success payload missing required token fields.token_incompletez7MiniMax OAuth timed out before authorization completed.r  )r  r   r  r  r(  MINIMAX_OAUTH_GRANT_TYPEr  r"  r   r)  r6  rK  allr  )r  r:   r<   r-  r  r  r  r  r  rb  r  r0  r`  r  r  r  s                  @rL   _minimax_poll_tokenr
    sx    $%%F
j//C-c&AAA .<::<<#a++-3,677H
**,,
!
!;;,,,6&&!.	  !D,   
 
	)1>hmmoooBGG 	 	 	GGG	 3&&;;{B//52::<HHYHMC:(8y::(/F   
 X&&WJ(/E    Y____/^_____ R,3E    NHQ **,,
!
!T A y   s   C* *C98C9c                    t                      5  t                      }t          |d|            t          |           ddd           dS # 1 swxY w Y   dS )zGPersist MiniMax OAuth state to Hermes auth store (~/.hermes/auth.json).r}   N)r  r4  r;  r  )r  r=  s     rL   _minimax_save_auth_stater    s    			 % %%''
Z*EEE$$$% % % % % % % % % % % % % % % % % %s   /AAAr   r'  r  r   c                ~   t           d         }| dk    r|j        d         }|j        d         }n|j        }|j        }t	                      \  }}}t                      rd}t          d|  d           t          d|            t          j        t          j	        |          d	d
id          5 }	t          |	||j        ||          }
t          |
d                   }t          |
d                   }t                       t          d           t          d|            t          d|            |rAt                      r3t          j        |          rt          d           nt          d           |
                    d          }|t#          |          nd}t          d           t%          |	||j        ||t#          |
d                   |          }ddd           n# 1 swxY w Y   t'          j        t*          j                  }t/          t#          |d                   |          }t1          dt#          ||                                z
                      }d| |||j        t4          |                    dd          |d         |d         |                    d           |                                t'          j        |t*          j        !                                          |d"}t;          |           t          d#           |                    d$          x}rt          d%|            |S )&z?Run MiniMax OAuth flow, persist tokens, return auth state dict.r}   r  r   r   Fz#Starting Hermes login via MiniMax (z
) OAuth...Portal: rQ  r  T)r  r!  follow_redirects)r:   r<   r  r>  r.  r-  r  r  r  r  r  r0  NzWaiting for approval...r  )r:   r<   r-  r  r  r  r  r   rZ  r[  r   rM  r\  r  )rM  r   r:   r;   r<   r=   rZ  r   rM  r\  r!  r  r  u#   ✓ MiniMax OAuth login successful.notification_messagezNote from MiniMax: )r   r@   r:   r;   r  r  r  r'  r  r  r  r<   r5   r$  r  r  r6  r   r
  r   r  r   r  r  r  r  r  r  r  r  )r   r'  r  r   r:   r;   r  r  r>  r  	code_datar  r-  interval_rawr  
token_datar  expires_at_unixexpires_in_sr  r  s                        rL   _minimax_oauth_loginr    s   
  0G~~!-(<=$]+BC!1$7!3!5!5Hi 	
B
B
B
BCCC	
&_
&
&'''	emO<<');<'+
- 
- 
- 
06.O'$E
 
 
	
 y);<==	+.//	n.,..///:y::;;; 	V799 	V/00 V;<<<<TUUU }}Z00+7+Cc,''''((((O'x9\233#
 
 

3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
B ,x|
$
$C8J|$%%3  O q#o?@@AAL $*0&$ nn\8<<">2#O4"~66}},_NNNXXZZ" J  Z(((	
2333nn3444s +)C))***s   /D-G((G,/G,)r  forcer  c          	        |                      d          st          dddd          	 t          j        |                      dd                                                    }n# t
          $ r d	}Y nw xY wt          j                    }|s||z
  t          k    r| S | d
         }t          j	        t          j
        |          d          5 }|                    | dd| d         | d         dddd          }ddd           n# 1 swxY w Y   |j        dk    rV|j                                        t          fddD                       }t          d|j        p|j         dd|          |                                }	|	                     d          dk    rt          dddd          t          j        t&          j                  }
t+          t-          |	d                   |
          }t/          dt-          ||
                                z
                      }t1          |           }|                    |	d         |	                     d| d                   |
                                t          j        |t&          j                                                   |d!           t9          |           |S )"zBRefresh MiniMax OAuth access token if close to expiry (or forced).rM  z:MiniMax OAuth state has no refresh_token; please re-login.r}   no_refresh_tokenTrL  r  r9   rR  r:   )r  r  r   r<   r  rO  r  rP  r  Nr#  c              3      K   | ]}|v V  	d S rT  rA   )r   rg  ra  s     rL   r   z/_refresh_minimax_oauth_state.<locals>.<genexpr>E  s?       Z ZAa4i Z Z Z Z Z ZrK   )r  r  invalid_refresh_tokenzMiniMax OAuth refresh failed: r  r  r  z-MiniMax OAuth refresh did not return success.r  r  r   r   r  )r   rM  r!  r  r  )r6  rK  r   r  r  r   r  "MINIMAX_OAUTH_REFRESH_SKEW_SECONDSr'  r  r  r(  r)  r  r   r  r  r"  r  r   r  r  r   r  rJ   r  r  r  r  )r>  r  r  r  r  r:   r  r`  r  r  now_dtr  r  	new_statera  s                 @rL   _refresh_minimax_oauth_stater!  "  sg   
 99_%% 
H$+=PT
 
 
 	
+EIIlB,G,GHHRRTT

   



)++C j3&*LLL-.O	emO<<'+
- 
- 
- 
06;;,,,-";/!&!7  !D,   
 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 s""}""$$ Z Z Z ZXZ Z Z Z ZVX]-Th>TVV$+;$
 
 
 	

 mmooG{{8	));$+;!
 
 
 	

 \(,''F8GL!""  O q#o0@0@0B0BBCCDDLUI/ _eO6LMM'')),_NNNXXZZ"     Y'''s#   :A& &A54A5/DDDc                   |j         r|                     d          sdS dD ]}|                     |d           d|j        pdt	          |          ddt          j        t          j                  	                                d| d	<   	 t          |            dS # t          $ r&}t                              d
|           Y d}~dS d}~ww xY w)a  Wipe dead tokens from auth.json after a terminal refresh failure.

    Shared by both the eager-resolve path and the lazy per-request token
    provider. Mirrors the Nous / xAI-OAuth / Codex-OAuth quarantine pattern
    so subsequent calls fail fast without a network retry.
    rM  Nr  r}   r  r  Tr  r	  z6MiniMax OAuth: failed to persist quarantined state: %s)rO  r6  r[  rN  r5   r   r  r   r  r  r  r   r   r*  )r>  r  r  r  s       rL   -_minimax_oauth_quarantine_on_terminal_refreshr#  d  s       UYY%?%? Z  		"d#,,s88+ l8<((2244   E
Z ''''' Z Z ZMyYYYYYYYYYZs   	B 
C
$CC
Callable[[], str]c                     dd} | S )u  Return a zero-arg callable that yields a fresh MiniMax access token.

    The Anthropic SDK caches ``api_key`` as a static string at construction
    time, so a session that resolves credentials once at startup will keep
    sending the same bearer until MiniMax's server returns 401 — typically
    ~15 minutes in, because MiniMax issues short-lived access tokens.

    Returning a *callable* instead of a string lets us hook into the
    existing Entra-ID bearer infrastructure in
    :mod:`agent.anthropic_adapter`: ``build_anthropic_client`` detects a
    callable and routes through ``_build_anthropic_client_with_bearer_hook``,
    which mints a fresh ``Authorization`` header on every outbound request.
    Each invocation re-reads the persisted state from ``auth.json`` and
    calls :func:`_refresh_minimax_oauth_state` — that helper is a no-op
    when the token still has more than ``MINIMAX_OAUTH_REFRESH_SKEW_SECONDS``
    of life left, so the steady-state cost is one file read + one
    timestamp compare per request.

    Reading state fresh each time also means a refresh persisted by one
    process (CLI, gateway, cron) is immediately visible to every other
    process sharing the same ``auth.json``.
    r   r5   c                 4   t          d          } | r|                     d          st          dddd          	 t          |           } n## t          $ r}t	          | |            d }~ww xY w|                     d          }|st          dddd          |S )	Nr}   r   MNot logged into MiniMax OAuth. Run `hermes model` and select MiniMax (OAuth).not_logged_inTrL  z6MiniMax OAuth state has no access_token after refresh.no_access_token)r^  r6  rK  r!  r#  )r>  r  r  s      rL   _providez4build_minimax_oauth_token_provider.<locals>._provide  s    '88 	EIIn55 	#(QU   
	077EE 	 	 	9%EEE	 		.)) 	H(/@SW       A 
A+A&&A+r   r5   rA   )r*  s    rL   "build_minimax_oauth_token_providerr-  }  s    .   * OrK   )min_token_ttl_secondsas_token_providerr.  r/  c                N   t          d          }|r|                    d          st          dddd          	 t          |          }n## t          $ r}t	          ||            d}~ww xY w|rt                      }n|d         }d||d                             d	          d
dS )u  Return {provider, api_key, base_url, source} for minimax-oauth.

    When ``as_token_provider`` is True, ``api_key`` is a zero-arg callable
    that mints a fresh access token per call (proactively refreshing if
    the cached token is within ``MINIMAX_OAUTH_REFRESH_SKEW_SECONDS`` of
    expiry). This is what the runtime provider path uses so that long
    sessions survive MiniMax's short access-token lifetime — see
    :func:`build_minimax_oauth_token_provider` for the rationale.

    The default (string ``api_key``) preserves the historical contract for
    diagnostic call sites like ``hermes status`` that just want to know
    whether a valid token exists right now.
    r}   r   r'  r(  TrL  Nr;   rC  r  r6  )r^  r6  rK  r!  r#  r-  rE  )r.  r/  r>  r  rQ   s        rL   )resolve_minimax_oauth_runtime_credentialsr1    s    " $O44E 
		.11 
$?T
 
 
 	

,U33   5eSAAA  (9;;'#./66s;;	  r+  c                    t          d          } | r|                     d          sdddS 	 t          j        |                     dd                                                    }|t          j                    z
  dk    }n2# t          $ r% t          |                     d                    }Y nw xY w|d|                     dd	          |                     d          d
S )z3Return auth status dict for MiniMax OAuth provider.r}   r   Fr!  r  r9   r   r   r   )r  rM  r   r  )r^  r6  r   r  r  r  r   r   )r>  r  token_valids      rL   r"  r"    s    #O44E A		.11 A"@@@6+EIIlB,G,GHHRRTT
!DIKK/14 6 6 6599^44556 !#))Hh//ii--	  s   AB ,B10B1c                   t          | dd          pd}t          | dd           }t          | dd          pd}	 t          |||           dS # t          $ r0}t          t	          |                     t          d	          d}~ww xY w)
z"CLI entry for MiniMax OAuth login.r   Nr   r  Fr  r$   r  r#   )r  r  rK  r  rm  r  )r  r   r   r'  r  r  s         rL   _login_minimax_oauthr5    s    T8T**6hFt\5999LdIt,,4Gg	
 	
 	
 	
 	
 	
    $$%%%mms   A 
B+BB)	r:   r;   r<   r=   r'  r  r  r  on_verificationr6  $Optional[Callable[[str, str], None]]c        	   
     	   t           d         }	| p.t          j        d          pt          j        d          p|	j                            d          } |pt          j        d          p|	j                            d          }
|p|	j        }|p|	j        }t          j	        |          }|rdn|r|nd}t                      rd}t          d|	j         d	           t          d
|             |rt          d           n|rt          d| d           t          j        |ddi|          5 }t          || ||          }t          |d                   }t          |d                   }t!          |d                   }t!          |d                   }t                       t          d           t          d|            t          d|            |r5t#          j        |          }|rt          d           nt          d           |	  |||           n# t&          $ r Y nw xY wt)          dt+          |t,                              }t          d| d           t/          || |t          |d                   ||           }ddd           n# 1 swxY w Y   t1          j        t4          j                  }t9          |                    dd!                    }|                                |z   }t?          |                    d"                    p|
}||
k    rt          d#|            i d$| d"|d%|d&|                    d&          p|d'|                    d'd(          d)|d)         d*|                    d*          d+|                                 d,t1          j!        |t4          j        -                                           d|d.|du tE          |t                    r|ndd/d0dd1dd2dd3dd4dd5d}	 tG          ||d6          S # tH          $ r}|j%        d7k    r|                    d$tL                                        d          }tO          |          }t                       t          |           t          d8| d9           t                       t          d:           tQ          d           d}~ww xY w);zMRun the Nous device-code flow and return full OAuth state without persisting.rM   r  r  rC  r  FTzStarting Hermes login via z...r  z'TLS verification: disabled (--insecure)z$TLS verification: custom CA bundle (r  rQ  r  r  )r  r:   r<   r=   r/  r-  r  r0  r  r  r  r  u=     Could not open browser automatically — use the URL above.Nr#   r  r  r&   )r  r:   r<   r&   r  r  r   r;   z%Using portal-provided inference URL: r:   r<   r=   rZ  r[  r   rM  r!  r  r  r  r|  r$  r%  r&  r'  r(  r#  )r  rh  rf  z  Subscribe here: z/billingz<After subscribing, run `hermes model` again to finish setup.))r   r  r  r:   rE  r;   r<   r=   r'  r  r  r  r7   r  r7  r5   r   r  r  r   r  r  r?  rD  r   r  r   r  r  r6  r  r  r  r  r   r  rK  rN  r  rm  r  )r:   r;   r<   r=   r'  r  r  r  r6  r   requested_inference_urlr  r  r  r  r  r-  r  r0  r*  effective_intervalr  r  token_expires_inr  resolved_inference_urlr  r  r\  rP  s                                 rL   _nous_device_code_loginr=    s     'G 	#9-..	#9+,,	# "fSkk  	 	&9.//	&%fSkk	 
 .W.I"W]EmO,,G"*Ri1QTF 	
8w|
8
8
8999	
&_
&
&''' C78888	 CAYAAABBB	g:L/MV\	]	]	] -
ag*+	
 
 
 {+FGHHK011	\233
{:.//n.,..///:y::;;; 	W_%566F W;<<<<UVVV & 0)<<<<    !C2W$X$XYYN5GNNNOOO$+K677!"
 
 

M-
 -
 -
 -
 -
 -
 -
 -
 -
 -
 -
 -
 -
 -
 -
^ ,x|
$
$C*:>>,+J+JKK#33J:>>*>??@@ 	#"  !888N6LNNOOO?4 	Y 	((1E	
 	jnn\8<< 	
>2 	88 	s}} 	h,ZHLIIISSUU 	& 	%#-fc#:#:D
 
 	T  	!" 	#$ 	%& 	D'( 	 )J,,+
 
 
 	

    8...#!#: fSkk  (,,GGGG'NNN;z;;;<<<GGGPQQQQ--sQ   ,C"JHJ
H)&J(H))A!JJJP 
S'B%SSc                     	 t          d          pi } n# t          $ r Y dS w xY w|                     d          }t          |t                    sdS t
          |                                v S )a  Return True if the currently-held Nous token carries ``billing:manage``.

    Reads the persisted ``scope`` string saved at login (``_save_provider_state``
    stores ``token_data.get("scope") or scope``). A space-delimited match. Used by
    the lazy step-up: if False, the first billing call will 403 ``insufficient_scope``
    anyway, but checking up front lets a surface skip a doomed round-trip.
    rM   Fr=   )r^  r   r6  r   r5   NOUS_BILLING_MANAGE_SCOPEr  )r>  r=   s     rL   nous_token_has_billing_scoper@    sx    '//52   uuIIgEeS!! u$55s    
"")r'  r  r6  c           	        t          d          pi }t          d         }|                    d          }t          |t                    r|nd}g }|                                pt          dgD ]}|r||vr|                    |           t          |vr|                    t                     d	                    |          }	t          |                    d          pd|                    d          pd|                    d	          p|j        |	| ||
          }
t                      5  t                      }t          |d|
           t          |           ddd           n# 1 swxY w Y   	 t!          |
           n# t"          $ r Y nw xY w	 t%                       n# t"          $ r Y nw xY w|
                    d          }t          |t                    ot          |                                v S )u  Re-run the device flow requesting ``billing:manage`` and persist the result.

    The lazy step-up (plan D-A): triggered when a billing endpoint returns
    ``403 insufficient_scope``. Runs a fresh device-connect with
    ``inference:invoke tool:invoke billing:manage`` on the scope. The user must be
    an ADMIN/OWNER and tick "Allow terminal billing" in the portal for the minted
    token to actually carry the scope; otherwise the server silently downscopes and this
    returns False.

    Reuses the held credential's portal/inference URLs + client_id so the step-up
    targets the same deployment (incl. a preview via ``HERMES_PORTAL_BASE_URL`` set
    at the original login). Persists to the auth store + shared store + pool, exactly
    like ``_login_nous`` — but WITHOUT the model picker (this is a scope upgrade, not
    a fresh login).

    Returns True iff the new token carries ``billing:manage``.
    rM   r=   r9   ztool:invoker.   r:   Nr;   r<   )r:   r;   r<   r=   r'  r  r6  )r^  r   r6  r   r5   r  r  rI  r?  ry  r=  r<   r  r4  r;  r  rc  r   r  )r'  r  r6  priorr   
_raw_scopeprior_scoper  tokr=   r  r=  granteds                rL   step_up_nous_billing_scoperG    s]   . $F++1rE'G 7##J *:s ; ;C**KI!!##S(C]'S " " 	"3i''S!!! 	112333HHYE(		"344< 99%9::Bd))K((=G,=!''  J 
		 % %%''
Z<<<$$$% % % % % % % % % % % % % % % ,,,,   '))))    nnW%%Ggs##T(AW]]__(TTs6   */E%%E),E)1F 
FFF! !
F.-F.c                   t          | dd          pd}t          t          | dd                    }t          | dd          p't          j        d          pt          j        d          }	 d}t	                      }|r	 t                      }n# t          $ r d}Y nw xY wt                       |rt          d	|            nt          d
           	 t          d          	                                
                                }n# t          t          f$ r d}Y nw xY w|dv r0t          d           t          |          }|t          d           |jt          t          | dd          t          | dd          t          | dd          p|j        t          | dd          t          | dd           |||          }|d         }	t!                      5  t#                      }
|
                    d          }ddd           n# 1 swxY w Y   t!                      5  t#                      }t'          |d|           t)          |          }ddd           n# 1 swxY w Y   t+          |           t-                       t                       t          d           t          d|            d}	 |                    d          p|                    d          }t/          |t0                    r|st3          ddd           d!d"lm}m}m}m}m}m }  |            }t                       g }d#}|r |d          } |d$%          }|                    d&d#          }|rY	 d!d'l!m"}m#}  |d$%          } ||d()          pd#}n# tH          $ r d#}Y nw xY w ||||          \  }} |||d$*          \  }}n ||||          \  }}|                    d&d#          }|r8t          d+tK          |           d,           tM          |||||d|	|-          }nR|rA|ptN          (                    d.          } t          d/           t          |pd0|  d1           nt          d2           nj# tH          $ r]}!t/          |!t2                    rtS          |!          nt1          |!          }"t                       t          d3|"            Y d}!~!nd}!~!ww xY w|st!                      5  t#                      }|r||d<   n|*                    dd           t)          |           ddd           n# 1 swxY w Y   t                       t          d4           t          d5           dS tW          d|	|6          }#|r!tY          |           t          d7|            t          d8|# d9           dS # t          $ r t          d:           t[          d;          tH          $ r&}!t          d<|!            t[          d=          d}!~!ww xY w)>z&Nous Portal device authorization flow.r  Nr$   r  Fr  r   r!  z)Found existing Nous OAuth credentials at z,Found existing shared Nous OAuth credentialsz!Import these credentials? [Y/n]: rU  >   r9   rU  r  z3Rehydrating Nous session from shared credentials...r  uK   Could not refresh shared credentials — falling back to device-code login.r\  inference_urlr<   r=   r  )r:   r;   r<   r=   r'  r  r  r  r;   r  rM   r  r"  r$  r   z,No runtime API key available to fetch modelsr  r:  r   )get_curated_nous_model_idsget_pricing_for_providercheck_nous_free_tierpartition_nous_models_by_tier&union_with_portal_free_recommendations&union_with_portal_paid_recommendationsr9   Trr  r:   ro  zpaid Nous modelsrt  )	free_tierzShowing u=    curated models — use "Enter custom model name" for others.)rX  rZ  r\  r]  r^  r_  r`  rC  z#No free models currently available.r  z to access paid models.z,No curated models available for Nous Portal.z?Login succeeded, but could not fetch available models. Reason: z:No provider change. Nous credentials saved for future use.z4  Run `hermes model` again to switch to Nous Portal.)r?  zDefault model set to: r  z (model.provider=nous)r  r  zLogin failed: r#   ).r  r   r  r  rS  rM  r  r  r  r   r   r  r  r  r=  r<   r  r4  r6  r;  r  rc  r  r   r5   rK  r  rJ  rK  rL  rM  rN  rO  rv  rp  rq  r   r   r  r  rE  rm  r[  rG  r  r  )$r  r   r  r  r  r  rG  shared_pathr  r;   _prior_storeprior_active_providerr=  r-  selected_modelr  rJ  rK  rL  rM  rN  rO  r  rZ  r]  rX  rP  _portal_for_recsrp  rq  _account_info_portal_urlr  rP  rC  s$                                       rL   _login_nousrY    s   dIt44<OGD*e4455Hk4(( 	&9'((	&9_%% 

 )** 	i#577 # # #"#GGG FO+OOPPPPDEEE !"EFFLLNNTTVV		/0      			 ,,,KLLL:$3  
 %ghhh0 'lD A A#*4$#G#G!$T::Og>OdGT22!(|U!C!CC /!#	 	 	J ((<=  	H 	H+--L$0$4$45F$G$G!	H 	H 	H 	H 	H 	H 	H 	H 	H 	H 	H 	H 	H 	H 	H  	4 	4)++J VZ@@@'
33H	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	!,,,')))!""")x))*** S	_$..55W9W9WKk3// { B#(                   3244IGGG')"$ )226:: 10TBBB	#->>2CR#H#H  #1       
 )E(DQU(V(V(VBB -+=   "  " ,+ % 1 1 1.0+++1 *P)O!7,<* *&Iw 5R4Q!7d5 5 51I11 *P)O!7,<* *&Iw !nn%6;;G FpYpppqqq!8w'9&(;%+%7$/" " " $ F:#:BB3GG;<<<)X-X4-X-X-XYYYYDEEE 	_ 	_ 	_0:3	0J0JX',,,PSTWPXPXGGGG]T[]]^^^^^^^^	_  	 "## - --//
( <4IJ011NN#4d;;; ,,,- - - - - - - - - - - - - - - GGGNOOOHIIIF1&n
 
 
  	=~...;>;;<<<F;FFFGGGGG   "###oo   $s$$%%%mms&  ,V5 ?B V5 BV5 B5V5 3D V5 DV5 DB9V5 $H:V5 H

V5 H
V5 /IV5 IV5 !I"AV5 4B0Q0 %#N	 Q0 	NQ0 NCQ0 /V5 0
S:ASV5 SV5 *<T2&V5 2T66V5 9T6:/V5 +AV5 51X&!XXc                R   t          | dd          }|r0t          |          s!t          d|            t          d          t	                      }|p|pt                      }|st          d           dS t          |          }t          |          }t          |          s|rn|rt                       t          d| d           |r%t          j        d          rt          d	           dS |rt          d
           dS t          d           dS t          d| d           dS )z Clear auth state for a provider.rM  NzUnknown provider: r#   z#No provider is currently logged in.zLogged out of r  r  z)Hermes will use OpenRouter for inference.z9Run `hermes model` or configure an API key to use Hermes.z+Model provider configuration was unchanged.zNo auth state found for )r  r/  r  r  ra  rO  rM  r1  rn  rQ  r  r  )r  r   re  rl  should_reset_configrq  s         rL   logout_commandr\    sp   $
D11K 1+>> 0;00111mm ""FLFL&J&L&LF 3444A&II26::M6"" ;&9 ; 	%"$$$/}///000 	A29-A#B#B 	A=>>>>>  	AMNNNNN?@@@@@9999:::::rK   r,  )rQ   r5   r   r5   r   r5   r   r5   )r   r   r   r   r   r   )r   r5   r   r4   r   r   )r  )rQ   r5   r  r  r   r  )r$  r5   r   r5   )rZ  r   r   r   )r!  r   r   r_  )rZ  r   r   r5   )rZ  rK  r   r5   )r  r   r   rQ  )r   r   )r  r5   r  rQ  r  r   r   rR  )r   r   )r   r  )r   r?   )r  r   r  r  r  r  r  r5   )r  r  rT  )r  r  r   r?   )r=  r?   r  r  r   r   )r=  r?   r   r5   r   r  )r=  r?   r   r5   r   r  )r=  r?   r   r5   r>  r?   r   rR  )
r=  r?   r   r5   r>  r?   r  r  r   rR  )
r=  r?   r   r5   r>  r?   r%  r   r   rR  )r   r5   r   rR  )r   r5   r   r   )r   r5   r   r5   )r   rQ  r   r?   )r   r5   r?  r@  r>  rA  r   r   )r   r5   r  r5   r   rR  )r   r5   r  r5   r   r   )r   r5   r   r  )r   rQ  )r   rQ  r   r   r  )rq  r5   r   r5   )r  rQ  r  rQ  r  rQ  r   r5   r  )r  r   r  r   r   r   )r  r   r   r   )r   r   r   rQ  )r  r?   r   rR  )r  rQ  r   rQ  )r  r   r   r?   )r  r   r   r  )
r  r   r=   r   r  r   r  r   r   rQ  )
r  r   r=   r   r  r   r  r   r   r   )r>  r?   r   r   r   rR  )r   r   r  rQ  r   rR  )r  r   r  r   r   rQ  )r>  r?   r!  rQ  r   rR  )r>  r?   r   r   r  rQ  r   rR  )r>  r?   r   r?   )r   r   r  r   r   r   )rA  r?   r   r   )rF  r   r  r   r   r   )rK  )rA  r?   r  r  r   r?   )re  r?   r   rR  )rh  r   ri  r   rj  r   r   r?   )r  rQ  r   rt  )r  rQ  r   r5   r  )r~  rQ  r>  r  r   r5   )r>  r  r   r5   )r  )r  r   r   r5   )r  r5   r   r5   )r<   r5   r  r5   r=   r5   r>  r5   r  r5   r  r5   r   r5   )r  r5   r   r  )r  r5   r   r  )r  r5   r  r  r   r  )r  r?   r<   r5   r  r5   r  r5   r  r5   r  r5   r  r  r   r?   )r<   r5   rN  r5   r  r5   r  r5   r  r5   r  r  r   r?   )r>  r?   r  r  r   r?   )r  r5   r   r5   )r  r5   r!  rW  r   rR  )r]  r   r   r?   )
r=  r?   rA  r1   re  rQ  ri  r  r   rR  )rA  r1   re  r5   r%  r5   r   rR  )r  r5   r   r  )r   r5   rM  r5   r  r  r   r?   )rA  r1   r  r  r   r1   )r   r  )r   r  )r=  r?   r   r  )r>  r  r   r   )r=  r?   r   r   )r>  r?   r   rR  )rA  r?   r  r  r  r5   re  rQ  rv  r5   r   rR  )r   )r   r5   r  r   r   r   )r   r5   r   r   )r  r5   r   r5   r   r5   )r   r5   r  r5   r   r5   )r$   )r  r  r   r1   )
r   r5   rM  r5   r  r5   r  r  r   r?   )
rA  r?   r  r5   r  r5   r  r  r   r?   )rh  r   ri  r   rj  r_  r   r?   )r   r  )r  r  r  rQ  r  r  r   r  )
r  r)  r:   r5   r<   r5   r=   rQ  r   r?   )r  r)  r:   r5   r<   r5   r&   r5   r  r   r  r   r   r?   )r>  r?   r   r   r  )r  r   r   r   )r>  r?   rZ  rK  r  r5   r   rR  )r=  r?   rZ  rK  r  r5   r   r   )r  r  r   r  )
r  r)  r:   r5   r<   r5   rM  r5   r   r?   )
r;   r5   rQ   r5   r  r  r  r  r   rt  )r>  r?   r  r   r   r   )
r  r  r  r  r  rQ  rj  r   r   r5   )"r   r5   rM  r5   r<   r5   r:   r5   r;   r5   rZ  r5   r=   r5   r!  rQ  r  rQ  r$  rQ  r&  rQ  r  r  r  r  r  rQ  rh  r   r  r  r   r?   )
r>  r?   r  r  rh  r   r  r  r   r?   )re  r?   r%  rQ  )
r  r  r  r  r  rQ  rh  r   r   r?   )r   r  )r   r5   r   r?   )r   r5   r;   r5   r?  rQ  r   r   )
r  r5   rM  r5   r$  r5   rQ   r5   r   r   )r9   NNr9   r9   r9   r9   r9   )r  rt  rE  r5   rX  rY  rZ  r[  r\  r5   r]  r5   r^  r5   r_  r5   r`  r5   r   rQ  )r  r5   r   rR  )r   r4   r  r   r   rR  )r  r)  r=   r5   r   r?   )r  r)  r  r5   r&   r5   r  r   r  r   r   r?   )r  r  r'  r   r   r?   )r   rB   )r  r)  r:   r5   r<   r5   r  r5   r>  r5   r   r?   )r  r   r  r   r   r   )r  r   r  r   r   r  )r  r)  r:   r5   r<   r5   r-  r5   r  r5   r  r   r  r_  r   r?   )r  r?   r   rR  )r   r5   r'  r   r  r  r   r?   )r>  r?   r  r  r  r   r   r?   )r>  r?   r  rK  r   rR  )r   r$  )r.  r   r/  r   r   r?   )r   r4   r   rR  )r:   rQ  r;   rQ  r<   rQ  r=   rQ  r'  r   r  r  r  r   r  rQ  r6  r7  r   r?   )r'  r   r  r  r6  r7  r   r   (^  rH   
__future__r   r"  loggingr  r  r  r  r  rI  r  r7  r  r  r  r  r  
contextlibr   dataclassesr   r   r   r   http.serverr	   r
   r   pathlibr   typingr   r   r   r   r   r   r   r   urllib.parser   r   r   r'  r   r   r   r   r   r  r   r   agent.credential_persistencer   utilsr   r    r!   r"   	getLoggerrE   r   r  r   r  r  r  r  ra  r`  r  r?  r_  rw  r  !ACCESS_TOKEN_REFRESH_SKEW_SECONDSNOUS_INVOKE_JWT_MIN_TTL_SECONDSr?  r  r  MINIMAX_OAUTH_CLIENT_IDr  r  MINIMAX_OAUTH_GLOBAL_BASEMINIMAX_OAUTH_CN_BASEMINIMAX_OAUTH_GLOBAL_INFERENCEMINIMAX_OAUTH_CN_INFERENCEr  ro  DEFAULT_GITHUB_MODELS_BASE_URLDEFAULT_COPILOT_ACP_BASE_URLDEFAULT_OLLAMA_CLOUD_BASE_URLSTEPFUN_STEP_PLAN_INTL_BASE_URLSTEPFUN_STEP_PLAN_CN_BASE_URLr  r  
hermes_clir+   _HERMES_CLI_VERSIONr  'CODEX_ACCESS_TOKEN_REFRESH_SKEW_SECONDSXAI_OAUTH_ISSUERr  r  XAI_OAUTH_SCOPEr  r  r_  r^  &QWEN_ACCESS_TOKEN_REFRESH_SKEW_SECONDSr  r  r  r  r  )SPOTIFY_ACCESS_TOKEN_REFRESH_SKEW_SECONDSrZ  ry  rw  r2   rI   r7  r4   r   r  r   _list_providers_for_registryr  r7   r8   env_varsrB   _api_key_varsr  _base_url_vardisplay_namer$  r  r  r   r   r   r   r   r  r&  r.  rA  rH  r\  r  rK  r^  rd  rm  rl  r{  r  r  r  r  r  r  localr  r  r  r4  r  r  r5  r;  r#  r'  r*  r/  r1  r=  rP  rV  rX  r\  r^  ra  rj  rn  rp  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r   r+  r-  r0  r3  r5  r7  r@  rE  rJ  rd  rg  rq  rs  r{  r}  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r.  r  r?  r$  rV  r#  rg  rs  rw  r{  r  r  ry  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r(  r7  rD  rK  rO  rI  rM  rP  rZ  rc  rS  ri  rn  r  rq  rt  rz  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  rs  r   r  r
  r  r  r  r  r#  r:  r>  rG  rI  rK  rM  rO  rQ  rW  r  r  r  r  r  r  r  r  r  r  r  r  r  r
  r  r  r!  r#  r-  r1  r"  r5  r=  r@  rG  rY  r\  rA   rK   rL   <module>r     s"    $ # " " " " "   				   



  



                 % % % % % % ( ( ( ( ( ( ( ( ' ' ' ' ' ' ' ' O O O O O O O O O O       R R R R R R R R R R R R R R R R R R R R 6 6 6 6 6 6 6 6 6 6             D C C C C C C C M M M M M M O O O O O O O O O O O O		8	$	$LLLL   EEEMMMM   FFF     < H % 0 , 0 ' ( $' !"C () %@ 2 @ 9 G 4 2 !C A %' "3 !@ .  7 "G  F 6 = $======= $ $ $#$<':<< *- '& -PPP < R/DDD  )- %9 A ), &$B !; H [ A ,/ )\  "    y*      1          M0
NN%/5(   M0 NN"1	  M0  ..6,*  !M00 4"5	  1M0< .."0	  =M0H 5(&  IM0X ~~9M/  YM0h >>!$7/  iM0v nnM=*  wM0F 
>>9G'  GM0V >> 8@(
 
 
WM0l nn&7-  mM0z ~~ :-+  {M0J ^^8-)  KM0Z 
>>;)'  [M0j ~~=-+  kM0z ^^0!19)!!9N(BD D
 
 
{M0 M0P 6\-  QM0` ~~S/-  aM0p >> *JM7  qM0@ ..?0.  AM0P 8.,  QM0` 
>>0)'  aM0p nn@,*  qM0@ NN720  AM0P >> ;1/  QM0j <.,  kM0z >>=&&  {M0J nn:,*  KM0Z @.,  [M0j NN8,*  kM0z ~~L+  {M0J ^^31  KM0 M0  M M M Md	HHHHHH++-- H H8(((=I%%S\% 8bbbpppppppgggggimnn&4nx!-SX"|*:cl*0b'
 '
 '
#(# k 	H 	HF...,=ch,G!&)	H/H4  	 	 	D	   D 3    "     89 	 	 	 	 	 	) ) ) )r 4gY(S	<wi7S;>o>o>o  rH  ICEvEvEv  yN  O# # # # #L. . . .b5 5 5 5. / 1 1 1 1 1 1 1 1"   - - - -*   BK K K K&D D D D- - - -
 >B [ [ [ [ [ [   ,% % % %>! ! ! !H2 2 2 2 $IO%%  D D D DN .G     $'< '< '< '< '<T3 3 3 3 3l   <   0 0 0 0< < < <: 4 4 4 4 4 4 ) ) ) )$S S S S
? ? ? ?,L ,L ,L ,L ,Lf ,0	0, 0, 0, 0, 0, 0,f% % % %      ,9 9 9 9"- - - -0 0 0 0f% % % % %P	% 	% 	% 	%"   4  $H '+'+	H H H H H H^   "9 9 9 9   ( ( ( ( ,596 , ,      .7Y 8 8 8 . .     > > > >. 1:	$; 1 1     
, , , ,^
D 
D 
D 
D   ,
6 
6 
6 
6   & :     @ :     *      2 "&
 
 
 
 
 
Q Q Q Q Q "&#; #; #; #; #; #;R !%	      &/Y 0 & & "   C C C C6 6 6 6   2   > Ms I I I I IE E E E EP% % % %*   $ F	     @
 
 
 
6	 	 	 	 	4 4 4 4 4
 #&*    0 #&*( ( ( ( ((( ( ( ( (- - - - -! ! ! ! !
H H H H
! ! ! ! !
H H H H
4 4 4 4*1 1 1 10#+ #+ #+ #+R #$ $ $ $ $ $^ 04! ! ! ! ! !V ", , , , , ,d "= = = = = =D   $ I	C C C C C CL   &: : : :z\) \) \) \)D   J *3  
* 
*  
 
 
 
6 6 6 6r        KO ' ' ' ' ' 'd )- . . . . . .j ;?	b, b, b, b, b,J% % % % %8   ( "	C C C C C CL) ) ) )X   H   $ G	q q q q q qhF F F FR% % % %X#6 #6 #6 #6L    -1 / / / / / /dX X X X*P *P *P *P` +/"&(; ; ; ; ; ;>    $   D& & & &R5 5 5 5p. . . . .j !_ _ _ _ _ _L 	% % % % % %T   $*.	\ \ \ \ \ \F   (  $#+/	           N   6.E .E .E .EP . *9?,, 0 0 0 0(   2 5N     6       F@H @H @H @HF   <H H H H      "   .( ( ( (B   F "D D D D D DN7W 7W 7W 7W| "6* 6* 6* 6* 6* 6*r	 	 	 	 "## Au) u) u) u) u) u)~ #!% $#*.!##GK#d d d d d dT "GK     @  8 8 8 8 8 8vU U U U "##S S S S S St
 
 
 
8) 8) 8) 8)H # X\  \ \ \ \# # # #	# 	# 	# 	#   B1( 1( 1( 1(h>
 >
 >
 >
B*
 *
 *
 *
Z   >   <              FJ J J JZG G G GT% % % %^ $(< < < < <~   $A A A AT T T T      . $ $ $ $ $ $H 37.2!F F F F FR   "    "	GL GL GL GL GL GL\ "	AI AI AI AI AI AIN !' ' ' ' ' 'TF F F FV "C C C C C CLD D D DR& & & &% % % %P+ + + +
) ) ) )< < < <~% % % % D!P P P P P Ph 8<? ? ? ? ? ?DZ Z Z Z2, , , ,` %G#& & & & & &R   $     &*(,#!#<@I I I I I IX6 6 6 6( !<@	@U @U @U @U @U @UFI I I IX; ; ; ; ; ;sI   C
 
CCC C'&C')D0 0D:9D::CS   SS