
    )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& dd
l'm(Z(m)Z)m*Z* ddl+Z+ddl,m-Z-m.Z.m/Z/ ddl0m1Z1m2Z2 ddl3m4Z4 ddl5m6Z6m7Z7m8Z8  ej9        e:          Z;	 ddl<Z<n# e=$ r dZ<Y nw xY w	 ddl>Z>n# e=$ r dZ>Y nw xY wdZ?dZ@dZAdZBdZCdZDeDZEdZFdZGdZHeHZIdZJdZKdZLdZMdZNdZOdZPdZQdZRd ZSd!ZTd"ZUd#ZVd$ZWd%ZXd&ZYd'ZZd(Z[d)Z\dZ]d*Z^e^ d+Z_d,Z`d-Zad.Zbd/Zcd0ZddZed1Zfd2ZgdZhd3Zid4Zjd5Zkd6Zld7ZmdZnd8Zod9Zpd:q                    d;          Zrd<d=iZsd>etd?<   d@Zud!ZvdAZwe G dB dC                      Zxi dD exdDdEdFeAeBeCeEG          dH exdHdIdJeKK          dL exdLdMdNdOdPdQR          dS exdSdTdJeLK          dU exdUdVdJeUK          dW exdWdXdJeuK          dY exdYdZdNd[d\d]R          d^ exd^d_dNeVd`daR          db exdbdcddeWdef          dg exdgdhdNdidjdkR          dl exdldmdNdndodpR          dq exdqdrdNdsdtduR          dv exdvdwdNdxdyz          d{ exd{d|dNeYd}d~R          d exdddNdddR          d exdddNdddR          d exdddNdddR          i d exdddePeReMeNdeQeSd          d exdddNdddR          d exdddNdddR          d exdddNdddR          d exdddNd ddR          d exdddNdddR          d exdddNdddR          d exdddNdddR          d exdddNdddR          d exdddNdddR          d exdddNddd¬R          d exdddNdddǬR          d exdddNddd̬R          d exdddNdddѬR          d exdddNeXddլR          d exdddddd۬R          d exdddNdddR          Zydetd<   	 ddlzm{Z|  e|            D ]Z}e}j~        eyv re}j        dNk    se}j        se}j~        dv r) ed e}j        D                       Z ed e}j        D             d          Z exe}j~        e}j        pe}j~        dNe}j        epe}j        epdެR          eye}j~        <   e}j        D ]Zeeyvreye}j~                 eye<   n# e=$ r Y nw xY wddZdZddZh dZddddZddZddndg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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e@fdd/            Zddd1Zdd3Zdd5Zdd7Zd8d9dd;Zdd<Zdd=Zdd>Zddd?ZddBZddDZddEZddFZddGZddHZddIZdddJZddKZddMZ	 ddddNddRZddTZddWZddYZddZZ ed[h          Zd\etd]<   dd_Zdd`ZddcZddeIddddhZddeIddddiZddjddlZddddmZĐdddoZddpddrZdddsddtZ edXduh          ZȐddvZɐddwZʐddxZːddyZ̐dd{Zehfdd}ZΐdddZϐddZАdd8ehdddZѐddZҐddZӐd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ZdŐdZdȐdZdƐdZecfdɐdZddddʐdZddZdddːdZd~dd̐dZd~dd͐dZdd8endddÄZddĄZdΐdƄZddǄZddȄZ eh dɣ          Zd\etd<   dd˄Zdϐd΄ZdАdτZddЄZddќdѐdԄZd8d՜dҐdׄZ	 ddӐdڄZddԐd܄Zd~ddՐdބZd֐d߄ZdאdZdd8e]dddZddZd8d՜dҐdZ dddddؐdZdِ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d8ee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e@f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d8dddZddZdddeHdddZdeEd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+etd,<   dd-Z)dd.Z*dd/Z+dd0Z,dd1Z-dd2Z.dd3Z/dd4Z0ddd5Z1dd6Z2dd7Z3dd8Z4	 ddd:Z5dd;Z6dd<Z7dd=Z8dd>Z9dd?Z:dddސd@ddDZ;	 	 	 	 	 	 	 	 dddPZ<ddQZ=ddRZ>ddSddUZ?ddSddVZ@ddYZAd~dddZZBd~d8dd[dd^ZCdd_ZDd daZEddbZFddeZGddhZHddkZIddlZJdd8ddmddoZKdddpddrZLddsZMd	duZNeTddvd
dyZOddzZPdd{ZQddddd8dddd|dd}ZRdd~ZSddZTd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ListOptionalTuple)parse_qs	urlencodeurlparse)get_hermes_homeget_config_pathread_raw_config)OPENROUTER_BASE_URLsecure_parent_dir)$sanitize_borrowed_credential_payload)atomic_replaceatomic_yaml_writeis_truthy_value         .@zhttps://portal.nousresearch.comz)https://inference-api.nousresearch.com/v1
hermes-clizinference:invoke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https://auth.x.aiz!/.well-known/openid-configurationz$b1a00492-073a-47ea-816f-4c329264a828z>openid profile email offline_access grok-cli:access api:access	127.0.0.1i9  z	/callback 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/xai-grok-oauthz@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cloudcode-pa://googlez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__r7   r8   r9   r:   r   dictr=   r@   rA   r>       4/home/ubuntu/.hermes/hermes-agent/hermes_cli/auth.pyr1   r1      s         //GGGIIINNNO     IEOOOO!E$777E7777     rH   r1   nouszNous Portaloauth_device_code)r3   r4   r5   r7   r8   r9   r:   openai-codexzOpenAI Codexoauth_external)r3   r4   r5   r8   z
openai-apiz
OpenAI APIapi_keyzhttps://api.openai.com/v1)OPENAI_API_KEYOPENAI_BASE_URL)r3   r4   r5   r8   r@   rA   	xai-oauthz%xAI Grok OAuth (SuperGrok / Premium+)
qwen-oauthz
Qwen OAuthgoogle-gemini-clizGoogle Gemini (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)r3   r4   r5   r8   rA   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)r3   r4   r5   r8   r@   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)r3   r4   r5   r7   r8   r9   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.comr>   BEDROCK_BASE_URLazure-foundryzAzure Foundryr6   )AZURE_FOUNDRY_API_KEYAZURE_FOUNDRY_BASE_URLzDict[str, ProviderConfig]PROVIDER_REGISTRYlist_providers>   rc   customrW   
openrouterrh   rl   c              #  n   K   | ]0}|                     d           |                     d          ,|V  1dS 	_BASE_URL_URLNendswith.0vs     rI   	<genexpr>r     sH      ppAQZZ=T=Tp]^]g]ghn]o]opapppppprH   c              #  n   K   | ]0}|                     d           s|                     d          ,|V  1dS r   r   r   s     rI   r   r     sH      ggAK9P9PgTUT^T^_eTfTfgaggggggrH   returnr2   c                     ddl m}  t          d         j        D ](} | |          pt	          j        |d          }|r|c S )dS )aQ  Return the first usable Anthropic credential, or ``""``.

    Checks both the ``.env`` file (via ``get_env_value``) and the process
    environment (``os.getenv``).  The fallback order mirrors the
    ``PROVIDER_REGISTRY["anthropic"].api_key_env_vars`` tuple:

        ANTHROPIC_API_KEY -> ANTHROPIC_TOKEN -> CLAUDE_CODE_OAUTH_TOKEN
    r   get_env_valuer   r6   )hermes_cli.configr   r   r@   osgetenv)r   varvalues      rI   get_anthropic_keyr     sh     0///// ->  c""8biR&8&8 	LLL	2rH   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)rN   r   r   s      rI   _resolve_kimi_base_urlr     sB       *%% "!!rH   >   *****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)
isinstancer2   striplenlower_PLACEHOLDER_SECRET_VALUES)r   r   cleaneds      rI   has_usable_secretr   '  sZ    eS!! ukkmmG
7||j  u}}444u4rH   provider_idpconfigtuple[str, str]c                   | dk    re	 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.rW   r   )resolve_copilot_tokenget_copilot_api_tokenz#Copilot token validation failed: %sN)r6   r6   r   r6   	load_poolaccess_tokenruntime_api_keyzcredential_pool:)hermes_cli.copilot_authr   r   
ValueErrorloggerwarning	Exceptionr   r   r@   r   r   agent.credential_poolr   has_credentialspeekgetattrr2   )r   r   r   r   tokensourceexcr   env_varvalr   poolentrykeys                 rI    _resolve_api_key_provider_secretr	  3  s    i	\\\\\\\\1133ME6 <,,U33V;;< 	G 	G 	GNN@#FFFFFFFF 	 	 	D	v//////+    }W%%+2244S!! 	 <	 333333y%% 	AD((** 	AIIKKE Ae^R88aGEK\^`<a<a#hhnn&&$S)) A @; @ @@@    6s-   #- 
A'AA'&A'1B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.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)r3   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   )	rN   r  ep_idr!  probe_modelsr"  r  respr  s	            rI   detect_zai_endpointr+  p  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.
    rc   detected_endpointr!  key_hashr6   N   zZ.AI: using cached endpoint %sr3   r  r"  )r!  endpoint_idr  r"  r.  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   rG   hashlibsha256encode	hexdigestr   r'  r+  _save_provider_stateinfo)rN   r   r   
auth_storestatecachedr.  detecteds           rI   _resolve_zai_base_urlr>    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rH   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.r6   NFprovidercoderelogin_requiredmessager2   rC  rD  Optional[str]rE  r   r   Nonec               t    t                                          |           || _        || _        || _        d S N)super__init__rC  rD  rE  )selfrF  rC  rD  rE  	__class__s        rI   rL  zAuthError.__init__  s9     	!!! 	 0rH   )
rF  r2   rC  r2   rD  rG  rE  r   r   rH  )rB   rC   rD   rE   rL  __classcell__)rN  s   @rI   rA  rA    sX        66 "!&1 1 1 1 1 1 1 1 1 1 1 1rH   rA  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   rA  rE  rD  CODEX_RATE_LIMITED_CODErP  s    rI   is_rate_limited_auth_errorrT    s4     	5)$$ 	2&&	2J11rH   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   )r3  r   r   r2   r   	TypeErrorr   )r  rawsecondss      rI   _parse_retry_after_secondsrZ    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_requiredrJ   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   rA  r2   rT  rE  rD  rC  #_format_nous_entitlement_auth_errorrS  s    rI   format_auth_errorrc    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::rH   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_accountrf  rg  r   )rP  rf  rg  account_inforF  s        rI   rb  rb  '  s    	
 	
 	
 	
 	
 	
 	
 	

 43EEE88*
 
 
  	N	   JJJJs   $( 
55r  rG  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   r2   r   r4  r5  r6  r7  )r  r   s     rI   _token_fingerprintrq  :  sd    eS!! tkkmmG t>'..1122<<>>ssCCrH   c                 |    t          j        dd                                                                          } | dv S )NHERMES_OAUTH_TRACEr6   >   1onyestrue)r   r   r   r   )rX  s    rI   _oauth_trace_enabledrx  D  s8    
)("
-
-
3
3
5
5
;
;
=
=C,,,rH   )sequence_ideventry  fieldsrH  c                   t                      sd S d| i}|r||d<   |                    |           t                              dt	          j        |dd                     d S )Nrz  ry  zoauth_trace %sTF)	sort_keysensure_ascii)rx  updater   r9  r  dumps)rz  ry  r{  payloads       rI   _oauth_tracer  I  sp    !! &.G -!,NN6
KK $*WSX"Y"Y"YZZZZZrH   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   environr3  r   homeresolver   RuntimeError)pathreal_home_authresolveds      rI   _auth_file_pathr  W  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      rI   _global_auth_file_pathr  m  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  HOMEr6   r  r  Fr  )	r  existsr   r  r3  r   r  r   r1  )global_pathreal_home_env	real_roots      rI   _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_suffixr>   rH   rI   _auth_lock_pathr    s    ((111rH   	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+   ro  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         rI   
_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    rI   _auth_store_lockr    s       
/	
 
   	                 s   8<<	auth_filec                z   | 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                    rht          |                    d          t                    s(t          |                    d          t                    r|                    di            |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_portalrJ   )r  r  active_provider)r  r  AUTH_STORE_VERSIONr  loads	read_textr   r  shutilcopy2r   r   r   rG   r3  
setdefault)r  rX  r  corrupt_pathr  r  r  s          rI   r1  r1    s   ._..I @-B???@j,,..// @ @ @ ,,_==	MMMFLL1111 	 	 	D	+sL	
 	
 	

 .B????????@ #t 377;''..cgg/00$77 	{B'''
 #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:  c                   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..wro  r  )'r  r  r  r   r  r   nowr   utc	isoformatr  r  	with_namer4   r   getpiduuiduuid4hexr  r2   O_WRONLYO_CREATO_EXCLr  S_IRUSRS_IWUSRfdopenwriteflushfsyncr  r   O_RDONLYr  closer  unlinkchmod)r:  r  r  tmp_pathfdhandledir_fds          rI   _save_auth_storer  :  s   !!I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
 +F//J
 2F/3J
 1G9 8J
 9HJ
 HJ
 H7 "J
 7IJ
 (I: :
JJ
K(J64K6
K KKK
,K7 7
LLOptional[Dict[str, Any]]c                   |                      d          }t          |t                    r9|                     |          }t          |t                    rt          |          S t                      }|rc|                     d          }t          |t                    r9|                     |          }t          |t                    rt          |          S d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  N)r3  r   rG   r  )r:  r   r  r;  global_storeglobal_providersglobal_states          rI   r2  r2  k  s     {++I)T"" k**eT"" 	;; +,,L *'++K88&-- 	*+//<<L,-- *L)))4rH   r;  c                    |                      di           }t          |t                    si | d<   | d         }|||<   || d<   d S Nr  r  r  r   rG   )r:  r   r;  r  s       rI   r8  r8    sX    %%k266Ii&& ,"$
;{+	"Ik$/J !!!rH   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        rI   _store_provider_stater    sl     %%k266Ii&& ,"$
;{+	"Ik 4(3
$%%%4 4rH   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  r6   N)r  r1  r3  r   r  r   r:  s     rI   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 Nr6   )r   r   r   r/   r   
normalizeds     rI   is_known_auth_providerr    s;    #**,,2244J**Rj<R.RRrH   c                    | pd                                                                 }|t          v rt          |         j        S t                              ||           S r  )r   r   r   r4   r/   r3  r  s     rI   get_auth_provider_display_namer    sT    #**,,2244J&&& ,11!%%j+>>>rH   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)r1  r3  r   rG   r  itemslist)r   r:  r  global_poolr  maybe_global_poolmergedgp_key
gp_entriesexistingprovider_entriesglobal_entriess               rI   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KrH   entriesList[Dict[str, Any]]c                    t                      5  t                      }|                    d          }t          |t                    si }||d<    fd|D             | <   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.
    r  c                ^    g | ])}t          |t                    rt          |          n|*S r>   )r   rG   r   )r   r  r   s     rI   
<listcomp>z)write_credential_pool.<locals>.<listcomp>  sM     
 
 
  %&&20DDD,1
 
 
rH   Nr  r1  r3  r   rG   r  )r   r%  r:  r  s   `   rI   write_credential_poolr+    s     
		 , ,%''
~~/00$%% 	1D,0J()
 
 
 
 !
 
 
[
  
++, , , , , , , , , , , , , , , , , ,s   AA<<B B 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  r1  r  appendr  r   r  r:  
suppressedprovider_lists        rI   suppress_credential_sourcer2    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.r-  F)r1  r3  r   )r   r  r:  r0  s       rI   is_source_suppressedr4    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.
    r-  NFT)	r  r1  r3  r   rG   r  removepopr  r/  s        rI   unsuppress_credential_sourcer8    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.
    )r1  r2  r  s     rI   get_provider_auth_stater:  .  s     "##J
K888rH   c                 H    t                      } |                     d          S )z8Return the currently active provider ID from auth store.r  )r1  r3  r:  s    rI   get_active_providerr=  ?  s     !##J>>+,,,rH   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.
    r6   r  Tr   )load_configr  rC  r   rN   F)r   r   r1  r3  r   r   r?  r   rG   r   r5   r@   r   r   r   )r   r  r:  activer?  cfg	model_cfgcfg_provider_IMPLICIT_ENV_VARSr   r  s              rI   !is_provider_explicitly_configuredrE  E  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  Tr*  )r   r:  targetr  r  cleareds         rI   clear_provider_authrI  x  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  r1  r  r<  s    rI   deactivate_providerrK    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_structurer6   uC   Config issue detected — run 'hermes doctor' for full diagnostics:rP  ERRORWARNINGz  [z] u       → r  )	r   rN  severityr.  rF  hint
splitlinesjoinr   )rL  rN  issueslinesciprefix
first_hints          rI   %_get_config_hint_for_unknown_providerrZ    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?d@d?dAd?dBdCdDdCi dEdCdFdGdHdGdIdJdKdJdLdJdMdJdNdOdPdOdQdOdRdOdSdTdUdTdVdWdXdWdYdWdZdZdZdZd[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 	 t                      }
|
                    dg          }|r/|t          v r&t          |          }|                    dh          r|S n2# t          $ r%}t                              di|           Y dj}~ndj}~ww xY wt          t!          j        dk                    s!t          t!          j        dl                    rd`S 	 d^dmlm}  |d`                                          rd`S n2# t          $ r%}t                              dn|           Y dj}~ndj}~ww xY wt                                          D ]H\  }}|j        dok    r|dpv r|j        D ]*}t          t!          j        |dq                    r|c c S +I	 d^drlm}  |            rdOS n# t4          $ r Y nw xY wt          dsdtf          )ua~  
    Determine which inference provider to use.

    Priority (when requested="auto" or None):
    1. active_provider in auth.json with valid credentials
    2. Explicit CLI api_key/base_url -> "openrouter"
    3. OPENAI_API_KEY or OPENROUTER_API_KEY env vars -> "openrouter"
    4. Provider-specific API keys (GLM, Kimi, MiniMax) -> that provider
    5. Fallback: "openrouter"
    autoglmrc   zz-aizz.aizhipugoogler_   zgoogle-geminizgoogle-ai-studiozx-air   x.aigrokrQ   z
x-ai-oauthz
grok-oauthzxai-grok-oauthkimirh   zkimi-for-codingmoonshotzkimi-cnrl   zmoonshot-cnsteprn   zstepfun-coding-planzarcee-airq   arceeaiz	gmi-cloudrt   gmicloudzminimax-chinar   
minimax_cnzminimax-portalr{   zminimax-globalminimax_oauthalibaba_codingr   zalibaba-codingalibaba_coding_planclauder   zclaude-codegithubrW   zgithub-copilotzgithub-modelszgithub-modelzgithub-copilot-acpr\   zcopilot-acp-agentopencoder   zenzqwen-portalrR   qwen-clirS   z
gemini-clizgemini-oauth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-gatewayrT   r   r   )z	lm-studio	lm_studioollama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)rD  r  	logged_inz)Could not detect active auth provider: %sNrO   OPENROUTER_API_KEYr   z.Could not check OpenRouter credential pool: %srN   >   rW   rT   r6   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   aliasesr4   r   r3  r   rZ  rA  r1  get_auth_statusr   r'  r   r   r   r   r   r   r  r5   r@   agent.bedrock_adapterr  ImportError)r]  r[  r\  r  _PROVIDER_ALIASES_lp_pp_alias_config_hintmsgr:  r@  statuse
_load_poolpidr   r  r  s                      rI   resolve_providerr    sm     %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\)!( \o  qD)!( FR  Tg)!( iw  y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 .8jN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  , |E%''
 122 	f 111$V,,Fzz+&&  E E E@!DDDDDDDDE #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 *//11  W	)) )))/ 	 	G 7B!7!788 




	======   	9	    	3 &	   sU   	+D5 5
EE9AH 
H=H88H=#J+ +
K5KK M 
M M 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   r2   r   r   r   fromisoformatr   r  replacer   r  	timestamp)r   textparseds      rI   _parse_iso_timestampr  b  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      rI   _is_expiringr  s  s0    (88MtTY[[<788rH   
expires_inc                h    	 t          |           }n# t          $ r d}Y nw xY wt          d|          S )Nr   )r   r   r  )r  ttls     rI   _coerce_ttl_secondsr  z  sF    *oo   q#;;s    !!c                    t          | t                    sd S |                                                     d          }|r|nd S )N/)r   r2   r   rstrip)r   r   s     rI   _optional_base_urlr    sC    eS!! tkkmm""3''G'774'rH   zinference-api.nousresearch.comzFrozenSet[str]_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 defaultr  )r   r2   r   r   r   schemer   r   hostnamer  r  )r  r   r  s      rI   )_validate_nous_inference_url_from_networkr    s    , c3 tiikkG t'""   tt}SM	
 	
 	
 t;;;:O	
 	
 	

 t>>#s   A 
AA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   ro  )r   r2   countsplitr   base64urlsafe_b64decoder6  r  r  decoder   rG   )r  r  rX  claimss       rI   _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   r2   r  r  r   addr  r?   	frozensetr  _scope_values)r  scopespartr   items        rI   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MrH   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  r3  NOUS_INFERENCE_INVOKE_SCOPEr  r   r   r  r  r  )r  r:   r  r  r  r  r  skews           rI   _nous_invoke_jwt_statusr    s      &&F &%%e


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

5))
*
*	+ 
 #&00//
**U

Cq#o&&''D#U|$$ ::$)++,--((tJ%% 7664rH   c               ,    t          | |||          d u S )Nr  )r  )r  r:   r  r  s       rI   _nous_invoke_jwt_is_usabler    s1     	 !+		
 	
 	
 	rH   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 nousrJ   TrB  )r3  r  rA  )r;  r   r  reasons       rI   !_assert_nous_inference_jwt_usabler    s    
 *6)=EIIn%%%<E$ii  99\**  F
 ~
	B	B 	B 	B   rH   c                x    t                               d           t          d|t          |                      d S )Nz)Nous inference auth: using NAS invoke JWTnous_invoke_jwt_selected)ry  access_token_fp)r   r9  r  rq  r   ry  s     rI   _log_nous_invoke_jwt_selectedr  '  sI    
 KK;<<<"*<88     rH   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  r3  r   r   r  r   fromtimestampr   r  r  r   r2   )r  r  r  r  s       rI   _nous_jwt_expires_atr  4  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)r3  r   r2   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	            rI   #_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
!"""rH   r  c                   t          |t                    r|                                r|| d<   t          |            t	          |                     d          |           d S )Nr   r  )r   r2   r   r  r  r3  )r;  r   ry  s      rI   _select_nous_invoke_jwtr  e  st     ,$$ -););)=)= - ,n'...!YY~..     rH   r  c                >    d |                                  D             S )Nc                ,    i | ]\  }}|t           v||S r>   )"_NOUS_EFFECTIVE_STATE_IGNORED_KEYS)r   r  r   s      rI   
<dictcomp>z2_nous_effective_provider_state.<locals>.<dictcomp>~  s4       C888 	U888rH   )r  r;  s    rI   _nous_effective_provider_stater  }  s+     ++--   rH   c                
   t          |           }|                    d          }t          |t          t          f          sdS t	          |          t          j                    t          dt          |                    z   k    S )Nr  Fr   )r  r3  r   r   r  r  r  )r   r  r  r  s       rI   _codex_access_token_is_expiringr    si    --F
**U

CcC<(( u::$)++As</@/@(A(AABBrH   c                 4    t          j                    dz  dz  S )Nz.qwenzoauth_creds.json)r   r  r>   rH   rI   _qwen_cli_auth_pathr     s    9;; #555rH   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.rR   qwen_auth_missingrC  rD  ro  r  z)Failed to read Qwen CLI credentials from : qwen_auth_read_failedz Invalid Qwen CLI credentials in r  qwen_auth_invalid)	r   r  rA  r  r  r  r   r   rG   )	auth_pathdatar  s      rI   _read_qwen_cli_tokensr	    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  ro  r  r  r  r}  r  )r   r  r  r   r  r4   r   r  r  r  r  r  r2   r  r  r  r  r  r  r  r  r  r  r  r  r  r   r  r  r  )r
  r  r  r  fhs        rI   _save_qwen_cli_tokensr    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  )r  r  	expiry_mss      rI   _qwen_access_token_is_expiringr    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_tokenr6   z@Qwen OAuth refresh token missing. Re-run 'qwen auth qwen-oauth'.rR   qwen_refresh_token_missingr  !application/x-www-form-urlencodedr  r  Accept
grant_typer  r9   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.air  r    )r   r  r#  r%  expiry_date)r2   r3  r   rA  r$  r%  QWEN_OAUTH_TOKEN_URLQWEN_OAUTH_CLIENT_IDr   r&  r  r  r   rG   r   r  r  r  )
r
  r  r  responser  bodyr  r  expires_in_seconds	refresheds
             rI   _refresh_qwen_cli_tokensr-    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!  rR   Nr  r1  r3  r2   r8  r  r.  r:  r;  s      rI   _mark_qwen_oauth_activer2    s     
		 % %%''
 "99Z   	7 #E*$5 6 6E*Zu===$$$% % % % % % % % % % % % % % % % % %   AA::A>A>F)force_refreshrefresh_if_expiringrefresh_skew_secondsr4  r5  r6  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   r6   r&  z?Qwen OAuth access token missing. Re-run 'qwen auth qwen-oauth'.rR   qwen_access_token_missingr  HERMES_QWEN_BASE_URLr  rr  )rC  r!  rN   r  expires_at_msr  )r	  r2   r3  r   r   r  r-  rA  r   r   r  DEFAULT_QWEN_BASE_URLr   )r4  r5  r6  r
  r   should_refreshr!  s          rI    resolve_qwen_runtime_credentialsr=  ,  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,..//  rH   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)r5  r  rN   r:  )r  r  r  rN   r:  Fr  r  rP  )r   r=  r2   r3  rA  )r  r.  r  s      rI   get_qwen_auth_statusr@  L  s    #%%I
 1TJJJYii))yy++"YY77
 
 	
  
 
 
YXX
 
 	
 	
 	
 	
 	
 	

s   AA. .
B"8BB"B"c                   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 google-gemini-cli in auth.json.

    The actual OAuth tokens live in the Google credential file managed by
    agent.google_oauth. This function only writes a minimal provider-state
    entry (email 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.
    emailrS   Nr0  r1  s      rI   _mark_google_gemini_cli_activerC  k  s     
		 % %%''
 "99W 	1 w00E'NZ)<eDDD$$$% % % % % % % % % % % % % % % % % %r3  r4  c           	        	 ddl m}m}m}m} n(# t
          $ r}t          d| dd          |d}~ww xY w	  ||           }n2# |$ r*}t          t          |          d|j                  |d}~ww xY w |            }t          }d||d	|r|j
        ndt           |                      |r|j        nd
pd
|r|j        nd
pd
dS )z2Resolve runtime OAuth creds for google-gemini-cli.r   )GoogleOAuthError_credentials_pathget_valid_access_tokenload_credentialsz&agent.google_oauth is not importable: rS   google_oauth_module_missingr  NrD  google-oauthr6   )rC  r!  rN   r  r:  r  rB  
project_id)agent.google_oauthrF  rG  rH  rI  r  rA  r2   rD  !DEFAULT_GEMINI_CLOUDCODE_BASE_URL
expires_msrB  rL  )	r4  rF  rG  rH  rI  r  r   r.  r!  s	            rI   (resolve_gemini_oauth_runtime_credentialsrP  }  sy   
	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
    :S::(.
 
 
 		--MJJJ   HH(
 
 
 		 E0H' .3=%****,,--!&.%++B52+08u''b?R	 	 	s'    
4/4A A4
%A//A4c                     	 ddl m} m} n# t          $ r dddcY S w xY w |             } |            }||j        sdt          |          ddS d	t          |          d
|j        |j        |j        |j        dS )z>Return a status dict for `hermes auth list` / `hermes status`.r   )rG  rI  Fzagent.google_oauth unavailable)r  rP  Nznot logged inr?  TrK  )r  r  r  rN   r:  rB  rL  )	rM  rG  rI  r  r   r2   rO  rB  rL  )rG  rI  r  r.  s       rI   get_gemini_oauth_auth_statusrR    s    OJJJJJJJJJ O O O"-MNNNNNO!!##IE}E.}Y$
 
 	
 ^^ %)&  s    	List[str]c                    | pt                                           }d |                                D             }t                      }g }|D ]0}||vr*|                    |           |                    |           1|S )Nc                    g | ]}||S r>   r>   )r   r  s     rI   r)  z'_spotify_scope_list.<locals>.<listcomp>  s    :::tT:d:::rH   )DEFAULT_SPOTIFY_SCOPEr   r  r  r  r.  )r  
scope_textr  seenorderedr:   s         rI   _spotify_scope_listrZ    s    44;;==J::z//11:::FUUDG " "HHUOOONN5!!!NrH   c                F    d                     t          |                     S )Nr+   )rT  rZ  )r  s    rI   _spotify_scope_stringr\    s    88'	22333rH   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   r   HERMES_SPOTIFY_CLIENT_IDSPOTIFY_CLIENT_IDr9   r6   zPSpotify client_id is required. Set HERMES_SPOTIFY_CLIENT_ID or pass --client-id.r,   spotify_client_id_missingr  )r   r   r   rG   r3  r2   r   rA  r]  r;  r   
candidates	candidater   s         rI   _spotify_client_idre    s     0///// 	011)**",UD"9"9C		+t	J    	io2&&,,.. 	NNN	
Z(   rH   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_urir6   )r   r   r   rG   r3  DEFAULT_SPOTIFY_REDIRECT_URIr2   r   rb  s         rI   _spotify_redirect_urirk    s     0///// 	344,--%/t%<%<F		.!!!$$J    	io2&&,,.. 	NNN	''rH   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_urlr6   r  )	r   r   r   rG   r3  DEFAULT_SPOTIFY_API_BASE_URLr2   r   r  r;  r   rc  rd  r   s        rI   _spotify_api_base_urlrq    s    ////// 	344%/t%<%<F		.!!!$$J
    	io2&&,,..55c:: 	NNN	''rH   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_urlr6   r  )	r   r   r   rG   r3  !DEFAULT_SPOTIFY_ACCOUNTS_BASE_URLr2   r   r  rp  s        rI   _spotify_accounts_base_urlrv  	  s    ////// 	899*4UD*A*AK		%&&&t)J
    	io2&&,,..55c:: 	NNN	,,rH   @   lengthc                    t          j        t          j        |                                         d          }|                    d          d d         S Nasciir     r  urlsafe_b64encoder   urandomr  r  rx  rX  s     rI   _spotify_code_verifierr  	  C    

"2:f#5#5
6
6
=
=g
F
FC::c??4C4  rH   code_verifierc                    t          j        |                     d                                                    }t	          j        |                              d                              d          S Nro  r{  r  r4  r5  r6  digestr  r~  r  r  r  r  s     rI   _spotify_code_challenger   	  X    ^M0099::AACCF#F++227;;BB3GGGrH   c                    t          j        t          j        |                                         d          }|                    d          d d         S rz  r}  r  s     rI   _oauth_pkce_code_verifierr  %	  r  rH   c                    t          j        |                     d                                                    }t	          j        |                              d                              d          S r  r  r  s     rI   _oauth_pkce_code_challenger  *	  r  rH   r9   ri  code_challengert  c           
     >    t          | d|||d|d          }| d| S )NrD  S256)r9   response_typeri  r:   r;  code_challenge_methodr  z/authorize?)r   )r9   ri  r:   r;  r  rt  querys          rI   _spotify_build_authorize_urlr  /	  sH     $!'(   E  33E333rH   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  r6   >   	localhostr)   z?Spotify PKCE redirect_uri must point to localhost or 127.0.0.1.zBSpotify PKCE redirect_uri must include an explicit localhost port.r  )r   r  rA  r  portr  ri  r  hosts      rI   _spotify_validate_redirect_urir  D	  s    l##F}V+
 
 
 	

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

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

 fk0S00rH   expected_path3tuple[type[BaseHTTPRequestHandler], dict[str, Any]]c                H     d d d d d G  fddt                     }|fS )NrD  r;  rP  error_descriptionc                  &    e Zd Zd
 fdZddZd	S )?_make_spotify_callback_handler.<locals>._SpotifyCallbackHandlerr   rH  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  
   Not found.rD  r   r;  rP  r  r   r  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>ro  )r   r  send_responseend_headerswfiler  r   r  r3  send_headerr6  )rM  r  paramsr*  r  results       rI   do_GETzF_make_spotify_callback_handler.<locals>._SpotifyCallbackHandler.do_GETe	  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2rH   formatr2   argsr   c                    d S rJ  r>   rM  r  r  s      rI   log_messagezK_make_spotify_callback_handler.<locals>._SpotifyCallbackHandler.log_message|	      FrH   Nr   rH  r  r2   r  r   r   rH  )rB   rC   rD   r  r  )r  r  s   rI   _SpotifyCallbackHandlerr  d	  sL        	3 	3 	3 	3 	3 	3 	3.	 	 	 	 	 	rH   r  )r	   )r  r  r  s   ` @rI   _make_spotify_callback_handlerr  \	  sc    !	 F       "8   6 #F**rH        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)rB   rC   rD   allow_reuse_addressr>   rH   rI   _ReuseHTTPServerr  	  s        "rH   r  z*Could not bind Spotify callback server on :r  r,   spotify_callback_bind_failedr  poll_interval皙?TrG  kwargsdaemon      @rD  rP  r  r  z?Spotify authorization timed out waiting for the local callback.spotify_callback_timeout)r  r  r
   r  rA  	threadingThreadserve_foreverstartr  r  r  shutdownserver_closerT  r  )ri  r  r  r  r  handler_clsr  r  serverr  threadr  s               rI   _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c                    t          |           }|j        dk    rt          ddd          |j        pd}|t          k    rt          ddd          |j        st          ddd          ||j        |j        pd	fS )
Nr  z1xAI OAuth redirect_uri must use http://127.0.0.1.rQ   xai_redirect_invalidr  r6   z/xAI OAuth redirect_uri must point to 127.0.0.1.z?xAI OAuth redirect_uri must include an explicit localhost port.r  )r   r  rA  r  XAI_OAUTH_REDIRECT_HOSTr  r  r  s      rI   #_xai_validate_loopback_redirect_urir  	  s    l##F}? '
 
 
 	

 ? bD&&&= '
 
 
 	

 ; 
M '
 
 
 	

 fk0S00rH   originc                    ddh}| |v r| ndS )Nzhttps://accounts.x.air(   r6   r>   )r  alloweds     rI   _xai_callback_cors_originr  	  s'    
 	 G w&&66B.rH   c                r     d d d d dt          j                     G  fddt                    }|fS )Nr  c                  8    e Zd ZddZddZd fdZdd
ZdS )7_make_xai_callback_handler.<locals>._XAICallbackHandlerr   rH  c                <   | j                             d          }t          |          }|rp|                     d|           |                     dd           |                     dd           |                     dd           |                     d	d           d S d S )
NOriginzAccess-Control-Allow-OriginzAccess-Control-Allow-MethodszGET, OPTIONSzAccess-Control-Allow-Headersr  z$Access-Control-Allow-Private-Networkrw  Vary)r  r3  r  r  )rM  r  allow_origins      rI   _maybe_write_cors_headerszQ_make_xai_callback_handler.<locals>._XAICallbackHandler._maybe_write_cors_headers	  s    \%%h//F4V<<L 3  !>MMM  !?PPP  !?PPP  !GPPP  222223 3rH   c                    |                      d           |                                  |                                  d S )N   )r  r  r  )rM  s    rI   
do_OPTIONSzB_make_xai_callback_handler.<locals>._XAICallbackHandler.do_OPTIONS	  s@    s###**,,,rH   c           
        t          | j                  }|j        k    rE|                     d           |                                  | j                            d           d S t          |j                  }|                    dd g          d         |                    dd g          d         |                    dd g          d         |                    dd g          d         d}	 t          
                    d	|j        |d         d u|d         d u|d         d u| j                            d
          pdd d                    |d         r2t          
                    d|d         |d         pdd d                    n# t          $ r Y nw xY w|d         |d         |                     d           |                                  |                     dd           |                                  d}| j                            |                    d                     d S 5  d         sd         s                    |           d d d            n# 1 swxY w Y   |                     d           |                                  |                     dd           |                                  |d         rd}nd}| j                            |                    d                     d S )Nr  r  rD  r   r;  rP  r  r  zSxAI loopback callback received: path=%s has_code=%s has_state=%s has_error=%s ua=%sz
User-Agentr6   P   z;xAI loopback callback carries error=%s error_description=%sr   r  r  r  z<html><body><h1>xAI authorization not received.</h1><p>No authorization code was present in this callback URL. Return to the terminal and re-run <code>hermes auth add xai-oauth</code> to retry.</p></body></html>ro  zS<html><body><h1>xAI authorization failed.</h1>You can close this tab.</body></html>zU<html><body><h1>xAI authorization received.</h1>You can close this tab.</body></html>)r   r  r  r  r  r  r   r  r3  r   r9  r  r   r  r  r6  r  )rM  r  r  incomingr*  r  r  result_locks        rI   r  z>_make_xai_callback_handler.<locals>._XAICallbackHandler.do_GET	  se   di((F{m++""3'''  """
  ///fl++F

6D62215GdV44Q7GdV44Q7%+ZZ0CdV%L%LQ%O	 HKV$D0W%T1W%T1\%%l339r3B3?   G$ KKU )!"56<"dsdC  
     'HW,=,E""3'''..000  1KLLL  """%  
  W!5!5666  , ,v ,&/ ,MM(+++, , , , , , , , , , , , , , , s###**,,,^-GHHH  olnJT[[1122222s%   .BF 
FF-&II#&I#r  r2   r  r   c                    d S rJ  r>   r  s      rI   r  zC_make_xai_callback_handler.<locals>._XAICallbackHandler.log_message8
  r  rH   Nr  r  )rB   rC   rD   r  r  r  r  )r  r  r  s   rI   _XAICallbackHandlerr  	  s        	3 	3 	3 	3	 	 	 	
Q	3 Q	3 Q	3 Q	3 Q	3 Q	3 Q	3 Q	3f	 	 	 	 	 	rH   r  )r  Lockr	   )r  r  r  r  s   ` @@rI   _make_xai_callback_handlerr  	  s    !	 F .""Kd d d d d d d d d4 d d dL &&rH   preferred_port8tuple[HTTPServer, threading.Thread, dict[str, Any], str]c                   t           }t          }t          |          \  }} G d dt                    }| g}| dk    r|                    d           d }d }|D ]+}		  |||	f|          } n# t
          $ r}
|
}Y d }
~
$d }
~
ww xY w|t          d| d|  d| dd	          |t          |j        d
                   }d| d| | }t          j
        |j        ddid          }|                                 ||||fS )Nc                      e Zd ZdZdZdS )4_xai_start_callback_server.<locals>._ReuseHTTPServerTN)rB   rC   rD   r  daemon_threadsr>   rH   rI   r  r  E
  s        "rH   r  r   z&Could not bind xAI callback server on r  r  rQ   xai_callback_bind_failedr  r    http://r  r  Tr  )r  XAI_OAUTH_REDIRECT_PATHr  r   r.  r  rA  r   server_addressr  r  r  r  )r  r  r  r  r  r  ports_to_tryr  
last_errorr  r  actual_portri  r  s                 rI   _xai_start_callback_serverr  >
  s    #D+M4]CCK    .    ##LAF$(J  	%%tTlK@@FE 	 	 	JJJJJJ	~ZTZZNZZjZZ +
 
 
 		 f+A.//K@T@@K@@@L#%  F
 LLNNN66<//s   A++
B5A<<Br  manual_paste_redirect_urir  r
   r  threading.Threadr  r   c                  t          j                    t          d|          z   }|rJt          j                                        r,t                       t          d           t          d           	 t          j                    |k     r|d         s|d         r@||                                  |                                  |	                    d           S |rxt                      }|rh|                                rTt          |          }d|d	<   ||                                  |                                  |	                    d           S t          j        d
           t          j                    |k     |                                  |                                  |	                    d           nC# |                                  |                                  |	                    d           w xY wt                              dt          d|          |d         d u|d         d u           t!          ddd          )Nr  z6If xAI shows a Grok Build code instead of redirecting,z%paste that code here and press Enter.rD  rP  r  r  T_manual_paster  z`xAI loopback wait timed out after %.0fs with no usable callback (result.code=%s result.error=%s)z;xAI authorization timed out waiting for the local callback.rQ   xai_callback_timeoutr  )r  r  r  sysstdinisattyprintr  r  rT  _read_ready_stdin_liner   _parse_pasted_callbackr  r   r9  rA  )r  r  r  r  r   r  	raw_pastepasteds           rI   _xai_wait_for_callbackr  f
  sV    ~#c?";";;H  7SY%5%5%7%7 7FGGG5666!n))f~   	C     ) "244	 "!2!2 "3I>>F.2F?+! 	C    	 JsOOO n)) 	C     	C    
 KK	+C!!vd"wt#   E#   s   2(F= ;F= +F= =A G=c                     	 t           j                                        sdS ddl} |                     t           j        gg g d          \  }}}|sdS t           j                                        S # t
          $ r Y dS w xY w)zHReturn one pending stdin line without blocking, if the terminal has one.Nr   )r  r  r  selectreadliner   )r  ready_s      rI   r	  r	  
  s    
y!! 	4mmSYKR;;q! 	4y!!###   tts   A/ -A/ A/ /
A=<A=)previous_statetoken_payloadrequested_scopern  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:   r#  r$  r   r6   r  
oauth_pkce)r9   ri  rt  rn  r:   granted_scoper#  r   r  r  r  r  r5   )r   r  r   r  r  r3  r  r  rG   r  r2   r   r  )r  r9   ri  r  rt  rn  r  r  r  r  r;  s              rI   _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))
 
 %''}} **,, !#    & LrH   rD  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  r  authorization_code)r9   r  rD  ri  r  r  zSpotify token exchange failed: r,   spotify_token_exchange_failedr  r  zSpotify token exchange failed.r   r6   r   z7Spotify token response did not include an access_token.spotify_token_exchange_invalid)r$  r%  r   rA  r&  r  r   r  r   rG   r2   r3  )
r9   rD  ri  r  rt  r  r)  r  detailr  s
             rI   !_spotify_exchange_code_for_tokensr   
  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 )Nr  r6   z?Spotify refresh token missing. Run `hermes auth spotify` again.r,   spotify_refresh_token_missingTrB  r  r  r  r  r  r  zSpotify token refresh failed: spotify_refresh_failedr  r  z>Spotify token refresh failed. Run `hermes auth spotify` again.r   r   z9Spotify refresh response did not include an access_token.spotify_refresh_invalidr:   )r9   ri  r  rt  rn  r  )r2   r3  r   rA  re  rv  r$  r%  r   r&  r  r  r   rG   r  rk  rV  rq  )	r;  r  r  r9   rt  r)  r  r  r  s	            rI   _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          |          }|r1t          |          }t          |d|d           t          |           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
                                          t          |          t          |          |                    d          t          |                    dd
          pd
                                          d
S )Nr,   z>Spotify is not authenticated. Run `hermes auth spotify` first.spotify_auth_missingTrB  r  Fr  r   r6   z>Spotify access token missing. Run `hermes auth spotify` again.spotify_access_token_missingr#  r$  r  r:   r  r  )
rC  r   rN   r#  r!  r:   r9   ri  r  r  )r  r1  r2  rA  r   r  r3  r%  r  r  r2   r   rq  re  rk  )r4  r5  r6  r:  r;  r<  r   s          rI   #resolve_spotify_runtime_credentialsr)  6  sK    
		 ) )%''
$Z;; 	P"+!%	    m,, 	Y"5 	Y)%))L*A*ACWXXN 	)077E!*i5QQQQZ(((#) ) ) ) ) ) ) ) ) ) ) ) ) ) )& 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  s   BB88B<?B<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  r  r6   r   r5   r  r9   ri  r  r:   rn  )r  r5   r9   ri  r:   r  rn  has_refresh_token)r:  r3  r2   r   r   r  )r;  r  r  s      rI   get_spotify_auth_statusr,  f  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	 	 	rH   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_  rg  z0Saved HERMES_SPOTIFY_CLIENT_ID to ~/.hermes/.env)r   r/  r  SPOTIFY_DOCS_URLSPOTIFY_DASHBOARD_URL_is_remote_session
webbrowserr  r   inputr   EOFErrorKeyboardInterrupt
SystemExitrj  )r-  r/  rX  s      rI   _spotify_interactive_setupr8  y  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,   r9   rD  r6   ra  ri  )r-  r:   
no_browserF)r9   ri  r:   r;  r  rt  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.<Could not open the browser automatically; use the URL above.r  r  r  rP  r  zSpotify authorization failed: r;  z-Spotify authorization failed: state mismatch.r  )r9   rD  ri  r  rt  r  )r9   ri  r  rt  rn  r  zSpotify login successful!  Auth state: z.  Provider state saved under providers.spotifyz  Docs: )#r:  r   re  rA  r8  rj  rk  r\  r3  rv  rq  r  r  r  r  r  r  r  r0  _print_loopback_ssh_hintr2  _can_open_graphical_browserr3  r  r   r  r  r7  r   r2   r  r  r1  r  r  )r  existing_stateexplicit_client_idr9   r  ri  r:   rt  rn  open_browserr  r  state_nonceauthorize_urlopenedcallbackr  r  spotify_stater:  saved_tos                        rI   login_spotify_commandrJ    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    rI   r2  r2    s`     
y ")I"6"6 t 	 	 9S>> 	44	5rH   >   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   r2   r   r   c                    |                                  r,|                                                                  d         nd}t          j                            |                                          }|t          v S )Nr   r6   )r   r  r   r  basenamer   r[  )r   r  bases      rI   _names_console_browserz;_can_open_graphical_browser.<locals>._names_console_browser_  s_    ,1KKMMA##%%a((rw&&,,..---rH   BROWSERr6   FlinuxDISPLAYWAYLAND_DISPLAYr4   r^  T)r   r2   r   r   )
r3  r   r  r3  r  platformr   r   r   r   )_webbrowserr`  browser_envhas_display
controllerrd  s         rI   r@  r@  J  sN   & %$$$. . . .
 *..B//K --k:: u
|w'' JNN9%%J8I)J)J
 
  	; 	5 __&&

   uu
 	
FB'' 	:z2..	 
  ++I66 u4s   )B> >
CCrX  rG   c                   |                                  }ddddd}|s|S d}|                    d          r-	 t          |          }n# t          $ r |cY S w xY w|j        pd}n.|                    d          r|dd         }nd|v r|}n||d<   |S t          |d	
          }dD ]$}|                    |          }|r|d         ||<   %|S )u  Parse a pasted callback URL / query string into the loopback shape.

    Accepts any of:

    * full URL:  ``http://127.0.0.1:56121/callback?code=abc&state=xyz``
    * bare query string:  ``?code=abc&state=xyz``  or  ``code=abc&state=xyz``
    * bare code (no state, only used when the upstream omits state):
      ``abc-the-code-value``

    Returns ``{"code", "state", "error", "error_description"}`` with
    missing keys set to ``None`` so the loopback callsites can keep
    using the same validation path (state check, error check, etc.)
    they already use for the HTTP server output.  Regression for
    #26923 — formalises the curl-the-callback-URL workaround the
    reporter used while waiting for upstream support.
    Nr  r6   )r  zhttps://?r    r  rD  F)keep_blank_valuesr   )r   r   r   r   r  r   r3  )rX  strippedr  r  r  r  r  valuess           rI   r
  r
    s*   " yy{{H!	 F  E233 	h''FF 	 	 	MMM	"			S	!	! 	 "veu555F> $ $C 	$ )F3KMs   A AAc                   t                       t          d           t          d           t          d|             t          d           t          d           t          d           t          d           t          d           t          d	           t          d
           t          d           	 t          d          }n# t          t          f$ r d}Y nw xY wt	          |          S )aJ  Read a callback URL from stdin as a fallback for browser-only remotes.

    Used when ``--manual-paste`` is set or when the loopback listener
    cannot bind.  Returns the parsed callback dict (same shape as the
    HTTP handler output) so the existing state / error validation in
    the caller works unchanged.  See #26923.
    u   ─── Manual callback paste ─────────────────────────────────────z>After approving in your browser, your browser will try to load  z=which fails (the loopback listener is on this remote machine,u<   not on your laptop) — that is expected.  Copy the FULL URLz=from your browser's address bar of that failed page and pastez<it below.  A bare '?code=...&state=...' fragment also works.z8If the consent page shows the authorization code in-pagez;(xAI's current behavior) rather than redirecting, paste thezbare code value on its own.u   ───────────────────────────────────────────────────────────────zCallback URL: r6   )r  r4  r5  r6  r
  )ri  rX  s     rI   _prompt_manual_callback_pasterq    s    
GGG	  \  ]  ]  ]	
JKKK	
|

	
IJJJ	
HIII	
IJJJ	
HIII	
DEEE	
GHHH	
'(((	  J  K  K  K$%%'(   !#&&&s   8C 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      rI   _ssh_user_at_hostry    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           t                       t          d           t          d           t          d           |rt          d|            t          dt                      t          |           t                       dS )a  Print an SSH tunnel hint when running a loopback-redirect OAuth flow on a
    remote host. The auth server (xAI, Spotify, ...) 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 (e.g. the xAI Grok OAuth
    page); the generic OAuth-over-SSH guide is always shown after it.
    Nr6   >   ::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@No SSH client (Cloud Shell / Codespaces / web IDE)?  Re-run withzC`--manual-paste` to skip the loopback listener and paste the failedzcallback URL directly.zProvider docs:      zSSH/jump-box guide: )r2  r   r   r  r  r  ry  OAUTH_OVER_SSH_DOCS_URL)ri  r<  r  r  r  dividers         rI   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	GGG	
LMMM	
OPPP	
"### 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.
    NrL   z?No Codex credentials stored. Run `hermes auth` to authenticate.codex_auth_missingTrB  r
  zICodex auth state is missing tokens. Run `hermes auth` to re-authenticate.codex_auth_invalid_shaper   r  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)r
  r  	r  r1  r2  rA  r3  r   rG   r2   r   r  r:  r;  r
  r   r  s         rI   _read_codex_tokensr    s     ( 	, 	,)++J	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, &''
 ^<<E 
M#%!	
 
 
 	
 YYx  Ffd## 
W#+!	
 
 
 	
 ::n--LJJ//MlC(( 
0B0B0D0D 
W#2!	
 
 
 	
 mS)) 
1D1D1F1F 
X#3!	
 
 
 	
 		.11     ,00r  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   Nr  r  rL   r  r#   Tzmanual:device_codeFr  last_statuslast_status_atlast_error_codelast_error_reasonlast_error_messagelast_error_reset_at)r3  r   rG   r  r   )r:  r
  r  r  r   r  r  r%  prev_atr  r  refresh_this_entrys               rI   _sync_codex_pool_entriesr  F  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"&#' %)!"&*"#'+#$$?, ,rH   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  rL   r
  r  chatgpt	auth_moder"  )r  )r   r  r   r  r  r  r  r1  r2  r   r3  rG   r2   r   r8  r  r  )r
  r  r"  r:  r;  r  s         rI   _save_codex_tokensr    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                    r|                                st          dddd          t	          j        t          dt          |                              }t	          j        |ddi	          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.r  rL   r  TrB  r  r  r  r  r  r  r  r  r  r  r  Ni  r  z2Codex provider quota exhausted (429); retry after zs. Credentials are still valid.zfCodex 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  rP  rD  typerF  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   r  r  )r   r2   r   rA  r$  Timeoutr  r  Clientr%  CODEX_OAUTH_TOKEN_URLCODEX_OAUTH_CLIENT_IDr&  rZ  r   rR  r  rG   r3  r   r   r  r   r  r  r  )r   r  r  r  clientr)  retry_afterrF  rD  rE  errerr_objnested_code
nested_msgerr_descrefresh_payloadr  refreshed_accessupdatednext_refreshs                       rI   refresh_codex_oauth_purer    s<    	mS)) 
1D1D1F1F 
X#3!	
 
 
 	
 mCU?%;%;<<==G	g:L/M	N	N	N 	
RX;;!#%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=   )B::B>B>-F8K& &
K32K3'L< <
MMMc           	        t          t          |                     dd          pd          t          |                     dd          pd          |          }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   r6   r  r  )r  r2   r3  rG   r  )r
  r  r,  updated_tokenss       rI   _refresh_codex_auth_tokensr  G  s     )FJJ~r**0b11FJJ++1r22'  I
 &\\N%.~%>N>"&/&@N?#~&&&rH   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_HOMEr6   z.codexr  Nr
  r   r  r   u7   Codex CLI tokens at %s are expired — skipping import.)r   r   r   r2   r   r  
expanduseris_filer  r  r  r3  r   rG   r  r   r'  r   )
codex_homer  r  r
  r   r  s         rI   _import_codex_cli_tokensr  \  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                    	 t                      }nk# t          $ r^ t                      }|rLt          j        dd                                                              d          pt          }d||ddddcY S  w xY wt          |d	                   }t          |
                    d
d          pd                                          }t          t          j        dd                    }t          |           }	|	s|rt          ||          }	|	rt          t          t          t                     |dz                       5  t          d          }t          |d	                   }t          |
                    d
d          pd                                          }t          |           }	|	s|rt          ||          }	|	rGt#          ||          }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.
    HERMES_CODEX_BASE_URLr6   r  rL   r  Nr  rC  r!  rN   r  r  r  r
  r   $HERMES_CODEX_REFRESH_TIMEOUT_SECONDS20r  r  Fr  hermes-auth-storer  )r  rA  _pool_codex_access_tokenr   r   r   r  DEFAULT_CODEX_BASE_URLrG   r2   r3  r  r   r  r  r  AUTH_LOCK_TIMEOUT_SECONDSr  )
r4  r5  r6  r  
pool_tokenr!  r
  r   refresh_timeout_secondsr<  s
             rI   !resolve_codex_runtime_credentialsr  ~  s   "!##   -//
 		1266<<>>EEcJJ *) 
 +$%+ $&     	" $x.!!Fvzz."55;<<BBDDL#BI.TVZ$[$[\\-((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#    A$A97A9C	HHH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  r6   rL   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   r  T)r   rG   r3  r2   r   r   r  r  )r  r  reset_ats      rI   _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4rH   r   z!Codex pool fallback lookup failedT)exc_info)r  r<   r   r   )r  r1  r3  r   rG   r  r2   r   r   r   r'  )r:  r  r%  r  r  s        rI   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                   | 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          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 )NrQ   z`No xAI OAuth credentials stored. Select xAI Grok OAuth (SuperGrok / Premium+) in `hermes model`.xai_auth_missingTrB  r
  zGxAI OAuth state is missing tokens. Re-authenticate with `hermes model`.xai_auth_invalid_shaper   r6   r  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_tokenr  	discoveryri  )r
  r  r  ri  r  r  s         rI   _read_xai_oauth_tokensr    s    ( 	, 	,)++J	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, &''
 [99E 
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	  r  r  ri  r  r  c                  |Dt          j        t          j                                                                      dd          }t                      5  t                      }t          |d          pi }| |d<   ||d<   d|d<   |r||d<   |r||d	<   t          |d|           t          |           d d d            d S # 1 swxY w Y   d S )
Nr  r  rQ   r
  r  r  r  r  ri  )r   r  r   r  r  r  r  r1  r2  r8  r  )r
  r  ri  r  r:  r;  s         rI   _save_xai_oauth_tokensr  !  s(    |HL11;;==EEhPSTT			 % %%''
$Z==C h ,n)k 	+!*E+ 	1$0E.!Ze<<<$$$% % % % % % % % % % % % % % % % % %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{  ro  r  r   )r   r2   r  r   r  r  r  r  r6  r  r3  r   r  r  r  r   )r   r  partspayload_b64r  r  s         rI   _xai_access_token_is_expiringr  8  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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  rQ   xai_discovery_invalidr  r6   zxAI OIDC discovery z is missing a hostname: rc  .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  rA  r  r   r   )r  r   r  r  s       rI   _xai_validate_oauth_endpointr  J  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 !(
 
 
 	
 JrH   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.
    r6   r  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.rc  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   r  r   r   r   r   r  r  r   r   )r   r  rd  r  r  s        rI    _xai_validate_inference_base_urlr  s  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 )Nr  r  )r  r  zxAI OIDC discovery failed: rQ   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_endpointr6   token_endpointz;xAI OIDC discovery response was missing required endpoints.r   )r  r  )r$  r3  XAI_OAUTH_DISCOVERY_URLr   rA  r&  r  r   rG   r2   r   r  )r  r)  r  r  r  r  s         rI   _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`.rQ   r  TrB  r  r  r  r  r  r  r  r  r  r  r9   r  r  r   r  z'xAI token refresh failed with HTTP 403.r   r6   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>   r  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  r#  r$  r  r  )r   r  r  r  r#  r  )r   r2   r   rA  r  r  r$  r  r  r  r  r%  XAI_OAUTH_CLIENT_IDr&  r  r  r   rG   r3  r   r  r   r  r  r  )r   r  r  r  endpointr  r  r)  r  r  r  r  r  s                rI   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()ri  c          	        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 )Nr   r6   r  r  r  r  r#  r  r  r  )r  r2   r3  rG   r  )r
  r  ri  r  r,  r  s         rI   _refresh_xai_oauth_tokensr  >  s!    'FJJ~r**0b11FJJ++1r22%'	  I &\\N%.~%>N>"&/&@N?#}}Z   ;%.z%:z"}}\"".'0'>|$}}\"" ?'0'>|$#^4!~.	    rH   c           
     	   t                      }t          |d                   }t          |                    dd          pd                                          }t          t          j        dd                    }t          |                    d          pi           }t          |                    dd          pd                                          }t          |                    dd          pd                                          }	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          |           }
|
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        t.          j                                                  d|d<   t5          |d|d           t7          |           n2# t8          $ 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?          t          j        dd                                                               d          p9t          j        dd                                                               d          tB                    }d||d|                    d          ddS )Nr
  r   r6   "HERMES_XAI_REFRESH_TIMEOUT_SECONDSr  r  r  ri  r  r  Fr  )r  ri  r  rQ   r  r  runtime_refresh_failureTrC  rD  rF  r  rE  atlast_auth_errorr  z2xAI OAuth: failed to persist quarantined state: %sHERMES_XAI_BASE_URLr  r   r  r  r  r  r  )"r  rG   r2   r3  r   r  r   r   r   r  r  r  r  r  r  rA  $_is_terminal_xai_oauth_refresh_errorr1  r2  r7  rD  r   r  r   r  r  r  r  r   r   r'  r  r  DEFAULT_XAI_OAUTH_BASE_URL)r4  r5  r6  r  r
  r   r  r  r  ri  r<  r  _q_store_q_state	_q_tokens	_save_excr!  s                    rI   %resolve_xai_oauth_runtime_credentialsr  ]  s    "##D$x.!!Fvzz."55;<<BBDDL#BI.RTX$Y$YZZTXXk**0b11I'7<<BCCIIKKNtxx339r::@@BBL-((N [ 3 [6|EYZZ 0c%8Q2R2RTknqTq.r.rsss /	 /	)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!-00N" c(; c!>|Ma!b!b %% 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+/	 /	 /	 /	 /	 /	 /	 /	 /	 /	 /	 /	 /	 /	 /	b 0
	',,2244;;C@@ 	=9^R((..0077<<+  H  %00!  sc   (D,P*A
K!P*!P,P=C%O#"P#
P-PPPPPP**P.1P.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)r  re  certifisslcreate_default_contextwherer  )r  s    rI   _default_verifyr    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   rG   r3  r   r   r   r2   r  isfiler   r   r  r  r  )r  r  r  	tls_stateeffective_insecureeffective_caca_paths          rI   _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rH   r  httpx.Clientr7   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/coder9   r:   r  )r#   	user_codeverification_uriverification_uri_completer  intervalc                    g | ]}|v|	S r>   r>   )r   fr  s     rI   r)  z(_request_device_code.<locals>.<listcomp>  s    ;;;QQd]]q]]]rH   z%Device code response missing fields: z, )r%  raise_for_statusr  r   rT  )r  r7   r9   r:   r)  required_fieldsmissingr  s          @rI   _request_device_coder5    s     {{222
#(0b
   H ==??D  O <;;;/;;;G WU7ASASUUVVVKrH   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/tokenz,urn:ietf:params:oauth:grant-type:device_code)r  r9   r#   r+  r   r   z+Token response did not include access_tokenz1Token endpoint returned a non-JSON error responserP  r6   authorization_pending	slow_down   r  zUnknown authentication errorr  z*Timed out waiting for device authorization)r  r  r  min%DEVICE_AUTH_POLL_INTERVAL_CAP_SECONDSr%  r&  r  r   r   r2  r  r3  r  r  )r  r7   r9   r#   r  r  r  current_intervalr)  r  error_payload
error_codedescriptions                rI   _poll_for_tokenrA    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_DIRr6   r   r  shared)r   r   r   r   r  r  r  )overrider  s     rI   _nous_shared_auth_dirrF  S  sh     y1266<<>>H +H~~((***888888""$$x//rH   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  rD  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.)
rF  NOUS_SHARED_STORE_FILENAMEr   r  r3  r  r  r  r   r  )r  r  real_home_sharedr  s       rI   _nous_shared_store_pathrJ  g  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)rJ  r  r  r  _nous_shared_lock_holder)r  r  s     rI   _nous_shared_store_lockrM    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.Fr  r          r6   )	r   r  r#  r:   r9   r7   r8   r  r  >   Nr6   T)_read_shared_nous_stater3  r   r2   r   r  )
r;  rD  shared_refreshlocal_refreshshared_access_explocal_access_exprefresh_changedfresher_accessr  r   s
             rI   _merge_shared_nous_oauth_staterW    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4rH   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.
    r  r   Nr    r#  r$  r:   r9   r7   r8   r  r  )_schemar   r  r#  r:   r9   r7   r8   r  r  r  Tr  r  r  r  ro  r  r  r  nous_shared_store_written)r  refresh_token_fpz*Failed to write shared Nous auth store: %s)0r3  r   r2   r   DEFAULT_NOUS_SCOPEDEFAULT_NOUS_CLIENT_IDDEFAULT_NOUS_PORTAL_URLDEFAULT_NOUS_INFERENCE_URLr   r  r   r  r  rM  rJ  r  r  r   r  r4   r   r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  rq  r   r   r'  )	r;  r  r   rD  r  tmpr  r  r  s	            rI   _write_shared_nous_statera    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: %sr  r   )rJ  r  r  r  r  r  r  r   r   r'  r   rG   r3  r2   r   )r  r  r  r  r   s        rI   rP  rP    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r  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)rM  rJ  r  FileNotFoundErrorr  r   r   r'  )r  r  r  s      rI   _clear_shared_nous_staterg     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.rJ      r  r  r  r   rA  rC  rD  r   rE  r  s    rI   _is_terminal_nous_refresh_errorrl  .  sJ     	3	"" 	'LF"	'HRR	' %&&	rH   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.
    rQ   >   r  r  rj  rk  s    rI   r  r  8  sJ     	3	"" 	'LK'	'HPP	' %&&	rH   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.
    rL   >   r  r  r  r  r  rj  rk  s    rI   &_is_terminal_codex_oauth_refresh_errorro  I  sN     	3	"" 		'LN*		'H 
 
		' %&&rH   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   r  r  r  r  r  r  r  r  r  r  NrJ   Tr  r  )
r7  rD  r2   r   r  r   r  r  rg  !invalidate_nous_auth_status_cache)r;  rP  r  r  s       rI   _quarantine_nous_oauth_staterr  `  s       			#t
u:: l8<((2244   E
 V$$$%'''''rH   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  FrJ   zmanual:r  T!nous_pool_device_code_quarantined)r  r?  )r3  r   rG   r  NOUS_DEVICE_CODE_SOURCEr.  r  rD  )	r:  rP  r  r  r%  retainedremovedsingleton_sourcesr  s	            rI   _quarantine_nous_pool_entriesry    s    >>+,,DdD!! uhhvGgt$$ uHG02U<S2U2UV  eT"" 	uyy':':>O'O'OG 
V/z	
 	
 	
 	

 NrH   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   r  r9   r7   r8   r#  r$  r:   r  r  Fr  r  r   r  r9   r7   r8   r#  r:   r  r  r  r  r  updated_stater<   _reasonr2   r   rH  c                $    t          |            d S rJ  )ra  )r}  r~  s     rI   _persist_shared_refreshz>_try_import_shared_nous_state.<locals>._persist_shared_refresh  s    (77777rH   Tr  r4  on_state_updatenous_shared_import_failedrD  )
error_typer?  &shared_import_terminal_refresh_failurezShared Nous import failed: %s)r  )r}  r<   r~  r2   r   rH  )rM  r  r  rP  r3  r]  r^  r_  r\  refresh_nous_oauth_from_statera  rA  r  r  rB   r   rl  rg  r   r'  r   )r  rD  r;  r  r,  r  s         rI   _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	||
          )Nr7  zx-nous-refresh-tokenr  )r  r9   r  r   r   z%Refresh response missing access_tokenrJ   r  TrB  zRefresh token exchange failedrC  rE  rP  r  r  ri  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  rA  r   r2   r3  r   )r  r7   r9   r  r)  r  r>  r  rD  r@  reloginlowereds               rI   _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  verifyr8   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.r  r  r  r  r  r  z/modelsr  r  )r  Nr   z#/models request failed with status r  rP  z'Could not parse error response JSON: %srJ   models_fetch_failedr  r  r3   hermesmidr2   r   r?   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     rI   _model_priorityz*fetch_nous_models.<locals>._model_priorityN  sX    iikkS==s8OC<<HC//s8Os??s8O3xrH   r  )r  r2   r   r?   )r$  r  r  r3  r  r&  r  r2   r   r   r'  rA  r   r  rG   r   r   r.  sortfromkeys)r8   rN   r  r  r  r  r)  r@  r  r  r  r  	model_idsr  model_idr  r  s                    rI   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  )r3  r   r2   r   r  r  r   )r;  r  r  s      rI   _agent_key_is_usabler  \  s    
))K
 
 Cc3 syy{{ u%ii  99344As?3344	   rH   )r  r  r  r6  c           	        t                      5  t                      }t          |d          }|st          ddd          t	          |                    d                    p.t          j        d          pt          j        d          pt          	                    d          }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|           t)          |           |
cddd           cddd           S t#          |t                    r|st          ddd          t+          j        | r| nd          }t+          j        |ddi|          5 }	 t1          ||||          }nf# t          $ rY}t3          |          rDt5          ||d           t7          ||d           t'          |d|           t)          |            d}~ww xY w	 ddd           n# 1 swxY w Y   t9          j        t<          j                  }tA          |                    d                    }|d         |d<   |                    d          p||d<   |                    d          p|                    d          pd|d<   |                    d          p|                    d          |d<   |!                                |d<   ||d<   t9          j"        |#                                |z   t<          j                  !                                |d<   ||d<   ||d	<   |d u t#          |t                    r|ndd!|d"<   t'          |d|           t)          |           tI          |           |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.rJ   &Hermes is not logged into Nous Portal.Tr  r7   HERMES_PORTAL_BASE_URLNOUS_PORTAL_BASE_URLr  r9   r  r  r  r   r  ,No access token found for Nous Portal login.r  Nz2Session expired and no refresh token is available.r!   r  r  r  r  r7   r9   r  $managed_access_token_refresh_failurere  r  r#  r$  r:   r  r  Fr{  r  )%r  r1  r2  rA  r  r3  r   r   r^  r  r2   r]  r(  rM  r  r  rW  r   r  r8  r  r$  r  r  r  rl  rr  ry  r   r  r   r  r  r  r  r  ra  )r  r  r  r6  r:  r;  r7   r9   r  merged_sharedr   r  r  r  r,  r  r  
access_ttls                     rI   resolve_nous_access_tokenr  h  s;    
		 ^) ^)%''
$Z88 	8!%    uyy):;;<< 'y122'y/00' '
&++ 	 		+..H2HII	 (iTYZZZ$S39NPi5j5jkkk J	) J	):5AAM 99^44L!IIo66MlC00  B#%)     		, 7 79MNN $  1(VUCCC$Z000#J	) J	) J	) J	) J	) J	) J	))^) ^) ^) ^) ^) ^) ^) ^)J mS11  H#%)    m$POODQQG!#56     5%(7"+&3	! ! !II !   6s;; 54!#I   
 6&#I   
 -ZGGG(444               8 ,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 !VU;;;Z((($U+++(UJ	) J	) J	) J	) J	) J	) J	))^) ^) ^) ^) ^) ^) ^) ^)(J	) J	) J	) J	) J	) J	) J	) J	) J	))^) ^) ^) ^) ^) ^) ^) ^) ^) ^) ^) ^) ^) ^) ^) ^) ^) ^)s   C8Q1B)Q0Q1	AQ#J,%H98J,9
J	AJ	J	J, Q,J00Q3J04FQ?Q1Q	Q1Q	 Q11Q58Q5r$  r#  r:   r  r  r  r  r  r  r  r4  r  r#  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                    }|r||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.
    r  r$  r{  r|  r  r!   r  r  r  r   r:   r  r  Nr  r  O) and no refresh token is available. Re-authenticate with: hermes auth add nousrJ   TrB  z.No refresh token is available for Nous Portal.r  r7   r9   r  r  r#  r8   r  r  post_refresh_access_token)r]  r^  r  r_  r\  r   r(  r$  r  r  r  r3  r   r2   rA  r  r   r  r   r  r  r  r  r  r  rG   r  r  )r   r  r9   r7   r8   r#  r:   r  r  r  r  r  r  r  r4  r  r;  r  r  r  current_invoke_jwt_statusrefresh_token_valuer,  r  r  refreshed_urls                             rI   refresh_nous_oauth_purer    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\	]	]	] /'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EimmThFiFijjM <.;*+#&==??E- ",E,"*"8*,# # #ikk , *U-HIII)%000&&&_/' /' /' /' /' /' /' /' /' /' /' /' /' /' /'b 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   r6   r  r9   r"   r7   r8   r#  r$  r:   r  r  r  r  r  r  r  )r3  r  r^  r_  r\  )r;  r  r4  r  r  s        rI   r  r  2  s    ))E


 bC"		."%%		/2&&		+|,,		#%<==		&(BCC99\844ii!344IIm,,99\**))K(("YY'=>>'$$''+&&#'!   rH   )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"  rJ   Nc              3  :   K   | ]}|j         t          k    |V  d S rJ  )r  ru  )r   r  s     rI   r   z+persist_nous_credentials.<locals>.<genexpr>  s/      JJqah2I&I&I&I&I&I&IJJrH   )r   r   rG   r2   r   r  r1  r8  r  ra  nextr%  )r.  r"  r   r;  r:  r  s         rI   persist_nous_credentialsr  O  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   rJ   z7Failed to sync Nous credential pool from auth store: %sN)r   r   r   r   r'  )r   r  s     rI   _sync_nous_pool_from_auth_storer    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  r4  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          	                    d                    pt          j        d          pt                              d          }t          	                    d          pt                     }dEfd}t#          ||          }t%          j        | r| nd          }	t)          dt+          	                    d                               t%          j        |	ddi|          5 }
	                    d          }	                    d          }t/          |t                    r|st          ddd          t1          |	                    d          	                    d                     }|s|t3          t5          | d!z   t6                    "          5  t9                    rm	                    d          }	                    d          }t1          |	                    d          	                    d                     } |d#           |s|Xt/          |t                    r|s|pd$}t          d%| d&d|d'          |rd$n|pd(}t)          d)|t+          |          *           	 t;          |
|||+          }nQ# t          $ rD}t=          |          r/t?          |d,-           tA          |d,-            |d.            d}~ww xY wtC          j"        tF          j$                  }tK          |	                    d/                    }|}|d         d<   |	                    d          p|d<   |	                    d0          p	                    d0          pd1d0<   |	                    d          p	                    d          d<   tM          |	                    d                    }|r|}|'                                d2<   |d/<   tC          j(        |)                                |z   tF          j$        3          '                                d<   d         }d         }t)          d4|t+          |          t+          |          5            |d6           ddd           n# 1 swxY w Y   tU          |7           tW          |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tY                       	                    d<          }t/          |t                    r|st          d=dd>?          	                    d@          }t[          |          }|1t5          dAt]          |t_          j/                    z
                      n!tK          	                    dB                    }d||	                    dC          ||t`          t`          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.
    Nrp  rJ   r  Tr  Fr7   r  r  r  r8   NOUS_INFERENCE_BASE_URLr9   r  r2   r   rH  c                   t                    t                    k    rt          d|            d S 	 t          d           t                     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)ry  r  rJ   nous_state_persist_failed)ry  r  r  nous_state_persistedr  r   )ry  r  r[  r  T)r  r  r8  r  r   r  rB   rq  r3  rG   ra  )r  r  r:  persisted_statery  r;  state_persisteds     rI   _persist_statez8resolve_nous_runtime_credentials.<locals>._persist_state  s@   
 /u551/BBC C 0 +!   
 
$Z??? ,,,,   / +!#Cyy1	     &'!3EIIo4N4N!O!O 2599^3L3L M M    #5kkO"O
 %U+++++s    A 
B"&BBr  r!   nous_runtime_credentials_startr  )ry  r[  r  r  r  r   r  r:   r  r  r  r  !post_shared_merge_access_unusabler4  r  r  rB  access_unusablerefresh_start)ry  r  r[  r  runtime_access_refresh_failurere  'terminal_runtime_access_refresh_failurer  r#  r$  r  r  refresh_success)ry  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  )rC  r!  rN   key_idr  r  r  r  r  r2   r   rH  )1r  r  r  r  r1  r2  rA  rG   r  r3  r   r   r^  r  r_  r2   r]  r(  r$  r  r  rq  r  r   r  rM  r  r  rW  r  rl  rr  ry  r   r  r   r  r  r  r  r  r  r  r  r  r  r   r  NOUS_AUTH_PATH_INVOKE_JWT)r  r  r  r4  r7   r8   r9   r  r  r  r  r   r  invoke_jwt_statusr  refresh_reasonr,  r  r  r  previous_refresh_tokenr  rN   r  r  r  r:  r  ry  r;  r  s                             @@@@@rI    resolve_nous_runtime_credentialsr    s0	     *,,"3B3'K			 uA uA%''
$Z88 	DD%+dD D D D u++ uyy):;;<< 'y122'y/00' '
&++ 	 uyy)=>>?? *y233*)
&++	 	
 		+..H2HII	&	, &	, &	, &	, &	, &	, &	, &	, &	, &	,P !(iTYZZZ-? LMM,#/		/0J0JKK	
 	
 	
 	
 \'H>P3QZ`aaa k	ek 99^44L!IIo66MlC00 H H N)/$H H H H !8ii(( 99\22! ! !
  KD 1 =,SSVAVXq=r=rsss JD JD5e<< L',yy'@'@(-		/(B(B,C("'))G"4"4',yy'>'>- - -)
 ''JKKK$ ?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(QR[R_R_`tRuRu(v(v( ?1>./2}}m,.8l+.6.DMMOOj8X\/ / /#)++ l+ (-^'<(-o(>$-(3#16HI_6`6`1CM1R1R    ''BCCCUJD JD JD JD JD JD JD JD JD JD JD JD JD JD JDX .)    $)'    (7E#$*<E&'!*E+"eO'1&#'>'>HVVD E%LQk	 k	 k	 k	 k	 k	 k	 k	 k	 k	 k	 k	 k	 k	 k	Z 	?@@@kuA uA uA uA uA uA uA uA uA uA uA uA uA uA uAn  *')))ii$$Ggs## >7 >D!'n> > > 	> 122J(44M $ 	As=49;;.//000 +A!B!BCC  &))N++  +.	 	 	s   F W=B7WCU1(M<;U1<
O
	?O	O
	
FU1%W1U55W8U59AWW=W	W="W	#W==XXc            	         dd d d d ddd dS )NF)r  r7   r8   access_expires_atr  r+  inference_credential_presentcredential_sourcer>   r>   rH   rI   _empty_nous_auth_statusr  }  s)    "! $"(-!	 	 	rH   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   rJ   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  rO  r  priorityr   )r  r   r   )r  	agent_exp
access_expr  s       rI   _entry_sort_keyz3_snapshot_nous_pool_status.<locals>._entry_sort_key  so    ,WU<RTX-Y-YZZa^aI-ge\4.P.PQQXUXJ75*a88=A>>HzH955rH   r  r   Nr   r5   r6   r  oauthr"  unknownr7   r8   runtime_base_urlr!  r  r  Tpool:)
r  r7   r8   r   r  r  r+  r  r  r  )r  r   r   r  )r   r   r   r  r  r%  r  r   r2   r   r   r   r   r^  r   )r   r  r%  r  r  runtime_keyr   r5   r  is_portal_oauthr"  portal_status_urls               rI   _snapshot_nous_pool_statusr    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z7Optional[Tuple[float, Optional[float], Dict[str, Any]]]_nous_auth_status_cachec                     	 t                                                      j        S # t          $ r Y d S t          $ r Y d S w xY wrJ  )r  r  st_mtimerf  r   r>   rH   rI   _auth_file_mtimer    s]      %%''00   tt   tts   $' 
A	A A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  r>   rH   rI   rq  rq    s     #rH   c                     t          j                    } t                      }t          }|)|\  }}}||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_TTLrG   _compute_nous_auth_status)r  mtimer<  	cached_atcached_mtimecached_statusr  s          rI   get_nous_auth_statusr    s      .

CE$F17.	<E!!y$???&&&&((F"E4<<8MrH   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.rJ   r   r7   r8   r  r  r  r  r:  )
r  r7   r8   r  r  r+  r   r  r  r  Tr!  r  zruntime:r  portalr  )
r  r7   r8   r  r  r+  r  r  r  r  FrE  rD  N)r  rP  rE  r?  )	r:  r   r3  r  r  rA  r2   r   r  )r;  base_statusr.  refreshed_stater  s        rI   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 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   rL   Nr   r   r6   Tr  r  r  r"  r  r  r:  r  r  r  rN   r  r  rN   Fr  r:  rP  )r   r   r   r  r   r  r2   r  r   r  r3  rA  r   r  r  rN   r.  r  s         rI   get_codex_auth_statusr  :  s   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#*      
133o//00!IIn55;//ii))yy++
 
 	
  
 
 
o//00XX
 
 	
 	
 	
 	
 	
 	

1   B3B7 7
CCA;E 
F+E?9F?F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   rQ   r   r   r6   Tr  r  r  r"  r  r  r  r  rN   Fr   )r   r   r   r  r   r  r2   r  r   r  r3  rA  r  s         rI   get_xai_oauth_auth_statusr  k  s   333333y%% 	D((** 	KKMME E#4d;; :unb99   #@!#L#L %)&)/*;*;&<&<(/~t(L(L%1"N'%)*L*L"N"N#*      
577o//00!IIn55;//ii))yy++
 
 	
  
 
 
o//00XX
 
 	
 	
 	
 	
 	
 	

r  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).rN   
configuredFr6      rh   rl   )r  rC  r4   
key_sourcer!  r  )r   r3  r5   r	  rA   r   r   r   r   r8   r   r4   r   r   rN   r	  env_urlr!  s         rI   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 ']]  rH   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_COMMANDr6   COPILOT_CLI_PATHrW   HERMES_COPILOT_ACP_ARGS--acp--stdioN
acp+tcp://)r  rC  r4   commandr  resolved_commandr!  r  )r   r3  r5   r   r   r   shlexr  rA   r8   r  whichr   r   r4   )r   r   r  raw_argsr  r!  r  s          rI   $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	 	 	rH   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 |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.r6   r  Fr,   rJ   rL   rQ   rR   rS   r{   r\   r   rN   r   r   r  r  rC  zboto3 not installed)r  rC  rP  )r=  r   r   r,  r  r  r  r@  rR  get_minimax_oauth_auth_statusr  _get_azure_foundry_auth_statusr   r3  r5   r  r  r  r  )r   rG  r   r  s       rI   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   6E	 	EEc                 \   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t#          j        d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.
    rC  r   r   )r?  r   r  NrN   r6   r  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).rR  zyazure-identity is installed; live credential validation is skipped here. Run `hermes doctor` to verify token acquisition.zazure-identity check failed: rP  r   )r   r?  r   r   r   rG   r3  r2   r   r   agent.azure_identity_adapterr   r!  r"  	from_dictr:   r   r   r   r   )r9  r?  r   rA  rB  r  r!  r   r!  r"  	installed	entra_cfgidentity_configr  rN   s                  rI   r  r    s    '8D@@@@@@@@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- 788bBIF]_a<b<b 9 9 9)3R889)'22DKs=    ((B+F- -
G7GGG G5 5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|r|                    d          }n|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.
    rN   
Provider 'z' is not an API-key provider.r  r  r6   rT   r  r  rc   r  rC  rN   r!  r  )r   r3  r5   rA  r	  LMSTUDIO_NOAUTH_PLACEHOLDERrA   r   r   r   r   r8   r>  r  r
  s         rI   $resolve_api_key_provider_credentialsr2  D  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	 .>>#&&-  OOC(()		  rH   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]   r/  z&' is not an external-process provider.r  r  r6   r  r  rW   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\   r  process)rC  rN   r!  r  r  r  )r   r3  r5   rA  rA   r   r   r   r8   r  r  r  r  r   r  )r   r   r!  r  r  r  r  s          rI   -resolve_external_process_provider_credentialsr6  q  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  rH   default_modelc                ^   t                      5  t                      }| |d<   t          |           ddd           n# 1 swxY w Y   t                      }|j                            dd           t                      }|                    d          }t          |t                    rt          |          }nBt          |t                    r+|                                rd|                                i}ni }| |d<   |r-|                                r|                    d          |d	<   n|                    d	d           |                    d
d           |                    dd           |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  rC  r  r!  rN   api_moder6   Fr}  )r  r1  r  r   r  r  r   r3  r   rG   r2   r   r  r7  r   )	r   r8   r7  r:  config_pathconfigcurrent_modelrB  cur_defaults	            rI   _update_config_for_providerr?    s    
		 % %%''
(3
$%$$$% % % % % % % % % % % % % % % "##KTD999FJJw''M-&& ''			M3	'	' M,?,?,A,A  3 3 5 56			'Ij (06688 ( 2 9 9# > >	* 	j$''' MM)T"""MM*d###
  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  rC  )r   r   r3  r   rG   r2   r   r   )r<  r  rC  s      rI   _get_config_providerrA    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)rA  r   r   )r   s    rI   _config_provider_matchesrC    s8     u!![%6%6%8%8%>%>%@%@@@rH   c                    | sdS |                                                                  }|t          v ot          |          S )z?Return True when logout should reset the model provider config.F)r   r   r   rC  r  s     rI   '_should_reset_config_provider_on_logoutrE    sF     u""$$**,,J**S/G
/S/SSrH   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.
    >   rJ   rQ   rL   N)rA  )rC  s    rI   $_logout_default_provider_from_configrG     s%     $%%H8884rH   c                    t                      } |                                 s| S 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_  rC  r!  Fr:  )r   r  r   r3  r   rG   r   r   )r;  r<  r  s      rI   _reset_config_providerrI    s    !##K F JJwE% 4"j 3E*k6U;;;;rH   rC  r!  rN   r  rC  r!  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_warningrJ  NTzH========================================================================zSwitch anyway? [y/N]: F>   yrv  )
hermes_cli.model_cost_guardrL  r   r  rF  r4  r   r   r6  r5  )r  rC  r!  rN   rL  r   r)  s          rI   "_confirm_expensive_model_selectionrO  "  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  r=  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  r2   r   rG  c                >    | sd S rt          |           sd S | S )NrJ  )rO  )r  rX  rW  rV  s    rI   _confirmed_selectionz5_prompt_model_selection.<locals>._confirmed_selection[  sG     	4 	$F%%#	%
 %
 %
 	 4
rH   c              3  B   K   | ]}                     |          V  d S rJ  r3  )r   mrP  s     rI   r   z*_prompt_model_selection.<locals>.<genexpr>s  s-      &J&J!w{{1~~&J&J&J&J&J&JrH   c              3  4   K   | ]}t          |          V  d S rJ  )r   )r   r_  s     rI   r   z*_prompt_model_selection.<locals>.<genexpr>t  s(      //qCFF//////rH   r  r  r  Fpromptr6   
completioninput_cache_readTr6   r6   r6      c                    
rC                     | d          \  }}}d|d d|d }	r|d|d z  }| d | }n| }| k    r|dz  }|S )Nrd  r+   >rp  <u     ← currently in user^  )r  inpoutcache
price_partr_  _price_cache	cache_colr=  	has_cachehas_pricingname_col	price_cols         rI   _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rH   zSelect default model:z     r  rg  r+   Inrp  OutCachez  /Mtokz[2mz[0m)curses_radiolistc                &    g | ]} |          S r>   r>   )r   r  rs  s     rI   r)  z+_prompt_model_selection.<locals>.<listcomp>  s!    222366#;;222rH   zEnter custom model namezSkip (keep current)r  Upgrade at z for paid modelsr    z   u	     ── u    ──Nr  )selectedcancel_returnsr@  
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  r2   r   rG  )hermes_cli.modelsrZ  r.  r  r   anyr  r3  r   hermes_cli.curses_uirw  r^  r  r   r  extendrS  rT  r  r4  r5  r6  r  NotImplementedErrorr  
subprocessSubprocessErrorr2   	enumerater   r   ).r  r=  rP  rR  rT  rU  rV  rW  rX  rZ  _unavailabler\  rY  r  
all_modelspri  rj  
cache_readrk  default_idx
menu_titlepadheader_DIM_RESETrw  choices_upgrade_urlunavailable_footer
desc_linesheader_partr@  idxr   	num_widthinchoicers  rm  rn  ro  rp  rq  rr  s.    ``   ```                              @@@@@@@rI   _prompt_model_selectionr  D  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_configr?  r  r  N)r   r  r?  r   r3  rG   )r  r  r?  r<  s       rI   _save_model_choicer    sz     ;:::::::[]]F&**W%%t,, 0%-w	""$h/wKrH   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  r7  )r  s    rI   login_commandr    s;    	
8999	
4555	
RSSS
Q--rH   )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.rN   r6   r&   z6Existing Codex credentials found in Hermes auth store.!Use existing credentials? [Y/n]: rM  >   r6   rM  rv  rL   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  >   rM  rv  r  r  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)r
  r  r   display_hermes_homer>  
/auth.json)r  r3  r   r2   r  r  r4  r   r   r5  r6  r?  r  rA  r  r  r   r   r  _codex_device_code_loginr  r  )r  r   r  r!  _resolved_keyr  r;  
cli_tokens	do_importr!  r.  _dhhs               rI   _login_openai_codexr  &  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               2   ~|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                    }	t%          |||	          }
t'          |
d         |
                    d          |
                    dd          |
                    d                     t          d|
                    d	t                              }t                       t          d
           ddlm} t          d |             d           t          d| d           d S )NrN   r6   r&   z:Existing xAI OAuth credentials found in Hermes auth store.r  rM  >   r6   rM  rv  rQ   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  r  r:  Fmanual_paster  rC  r  r
  r  ri  r  r  r   r  r>  r  )r  r3  r   r2   r  r  r4  r   r   r5  r6  r?  r  rA  r  r   r2  r   _xai_oauth_loopback_loginr  r  r  )r  r   r  r!  rN   r  r;  r  rC  r  r.  r  s               rI   _login_xai_oauthr  p  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 ne<<==L%'!!  E
 h))K((YY~r22YY~..	    .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  noncec                X    dt           |t          |d||ddd
}|  dt          |           S )NrD  r  genericzhermes-agent)
r  r9   ri  r:   r  r  r;  r  planreferrerrk  )r  XAI_OAUTH_SCOPEr   )r  ri  r  r;  r  authorize_paramss         rI   _xai_oauth_build_authorize_urlr    sN      ($ (!'"  %DDy1A'B'BDDDrH   c           	        |st          ddd          d||t          |d}|r
||d<   d|d	<   	 t          j        | d
dd|t	          d|                    }n(# t
          $ r}t          d| dd          |d}~ww xY w|j        dk    ri|j                                        }	|j        dk    r t          d|	rd|	 ndz   dz   ddd          t          d|j         d|	rd|	 ndz   dd          	 |	                                }
n(# t
          $ r}t          d| dd          |d}~ww xY wt          |
t                    st          ddd          |
S ) u  POST the authorization code to xAI's token endpoint and return
    the parsed JSON payload.

    Sends ``code_verifier`` as required by RFC 7636 §4.5.  Also echoes
    ``code_challenge`` + ``code_challenge_method`` in the request body
    as a defense-in-depth measure for OAuth servers (xAI's among them,
    per #26990) that re-validate the challenge at the token step
    instead of relying solely on server-side session state captured
    during the authorize step.  Echoing the challenge is harmless for
    strict RFC-compliant servers — RFC 7636 doesn't forbid additional
    parameters at the token endpoint — and decisively fixes the
    ``code_challenge is required`` failure mode users hit on the
    loopback flow.

    Raises :class:`AuthError` on any non-2xx response or transport
    failure; the error message embeds the HTTP status code and the
    full response body so users can disambiguate cause at a glance.
    u   xAI token exchange refused locally: PKCE code_verifier is empty. This is a bug in Hermes — please report at https://github.com/NousResearch/hermes-agent/issues/26990.rQ   xai_pkce_verifier_missingr  r  r  rD  ri  r9   r  r  r  r  r  r  r  r  r  zxAI token exchange failed: xai_token_exchange_failedNr   r  z%xAI token exchange failed (HTTP 403).r   r6   u/   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. Set ``XAI_API_KEY`` and switch to ``provider: xai`` (API-key path) if available, or upgrade your subscription at https://x.ai/grok.r  FrB  z xAI token exchange failed (HTTP z).z*xAI token exchange returned invalid JSON: xai_token_exchange_invalidz2xAI token exchange response was not a JSON object.)rA  r  r$  r%  r  r   r&  r  r   r  r   rG   )r  rD  ri  r  r  r  r  r)  r  r*  r  s              rI   #_xai_oauth_exchange_code_for_tokensr    sa   >  
I !,
 
 
 	
 +$(& D  /!/(.$%: C,  o..
 
 
    /#// ,
 
 
 		 s""}""$$
 3&&8+/7''''R9(( %,!&    Gx/CGGG'+3#T###5 ,	
 
 
 	
--//   >>> -
 
 
 		 gt$$ 
@ -
 
 
 	

 Ns/   )A 
A>#A99A>6D 
D0D++D0r  rC  r  c           
        d=d}t          |           }|d         }|d         }d}|rdt           dt           t           }t	          |           t                      }	t          |	          }
t          j                    j	        }t          j                    j	        }t          |||
||	          }t          d
           t          |           t          |          }d}nt                      \  }}}}	 t	          |           t                      }	t          |	          }
t          j                    j	        }t          j                    j	        }t          |||
||	          }t          d
           t          |           t                       t          d|            t          |t                     |ret!                      sWt#                      rI	 t%          j        |          }n# t(          $ r d}Y nw xY w|rt          d           nt          d           	 t+          |||t-          d| dz            |          }n# t.          $ r}t1          |dd          dk    s
 |            s t                       t          d           t          d           t          d           t          d           t          d           t          |          }|                    d          |                    d          |d}Y d}~nd}~ww xY wnq# t(          $ rd 	 |                                 |                                 n# t(          $ r Y nw xY w	 |                    d           n# t(          $ r Y nw xY w w xY w|                    d          r2|                    d          p|d         }t/          d | d!d"#          |                    d$          }|                    d%          rd}||s|r|}||k    rt/          d&d!d'#          t;          |                    d          pd                                          }|st/          d(d!d)#          t?          ||||	|
| *          }t;          |                    d+d          pd                                          }t;          |                    d,d          pd                                          }|st/          d-d!d.#          |st/          d/d!d.#          tA          tC          j"        d0d                                          #                    d1          p9tC          j"        d2d                                          #                    d1          tH          3          }||t;          |                    d4d          pd                                          |                    d5          t;          |                    d6          pd7                                          pd7d8|||tK          j&        tN          j(                  )                                *                    d9d:          d;d<S )>u  Run the xAI OAuth PKCE flow.

    When ``manual_paste=True`` the loopback HTTP listener is skipped
    entirely and the user is prompted to paste the failed callback
    URL into stdin (regression fix for #26923 — browser-only remote
    consoles like GCP Cloud Shell / GitHub Codespaces / EC2 Instance
    Connect, where the laptop's browser can't reach 127.0.0.1 on the
    remote VM).  The same PKCE verifier, ``state``, and ``nonce`` are
    used for both paths so the upstream-side OAuth flow is identical.
    r   r   c                     	 t           t          t          j        dd                                 S # t          $ r Y dS w xY w)Nr  c                     dS )NFr>   r>   rH   rI   <lambda>zQ_xai_oauth_loopback_login.<locals>._stdin_supports_manual_paste.<locals>.<lambda>S  s    U rH   F)r   r   r  r  r   r>   rH   rI   _stdin_supports_manual_pastez?_xai_oauth_loopback_login.<locals>._stdin_supports_manual_pasteQ  sP    	C	8]]CCEEFFF 	 	 	55	s   03 
A Ar  r  Fr  r  )r  ri  r  r;  r  z+Open this URL to authorize Hermes with xAI:TzWaiting for callback on r;  z%Browser opened for xAI authorization.r=  g      >@	   r  rD  r6   r  z xAI loopback callback timed out.z9If your browser reached a failed 127.0.0.1 callback page,z:paste that FULL callback URL below to continue this login.z5You can also re-run with `--manual-paste` to skip thez!loopback listener from the start.NrP  r  r  r  zxAI authorization failed: rQ   xai_authorization_failedr  r;  r  z)xAI authorization failed: state mismatch.xai_state_mismatchz5xAI authorization failed: missing authorization code.xai_code_missing)r  rD  ri  r  r  r  r   r  z2xAI token exchange did not return an access_token.r  z2xAI token exchange did not return a refresh_token.r  r  r   r  r  r  r#  r$  )r   r  r  r  r#  r  r  zoauth-loopback)r
  r  ri  r!  r  r  r   r   )+r  r  XAI_OAUTH_REDIRECT_PORTr  r  r  r  r  r  r  r  r  rq  r  r?  XAI_OAUTH_DOCS_URLr2  r@  r3  r  r   r  r  rA  r   r3  r  r  rT  r2   r   r  r  r   r   r  r  r   r  r   r  r  r  )r  rC  r  r  r  r  r  allow_missing_stateri  r  r  r;  r  rE  rG  r  r  callback_resultrF  r  r  callback_staterD  r  r   r  r!  s                              rI   r  r  A  sc        %_55I&'?@/0N ^)- ) )0G )&) ) 	 	,L9991333MBB
 
 6#9%)
 
 
 	;<<<m0>>"8R8T8T5A	/===577M7FFNJLL$EJLL$E:'=)-  M ?@@@-   GGG;\;;<<<$\<NOOOO Z$6$8$8 Z=X=Z=Z Z#'_];;FF  # # #"FFF# ZABBBBXYYY+1#$'o.A$B$B.:    + + +C,,0FFF7799 G 8999QRRRRSSSMNNN9:::8FF<<''/HLL4I4I4QI&*######+   
	 
	 
	!!!##%%%%   C((((   
	 ||G 
122Ghw6G111 +
 
 
 	

 \\'**N ||O$$ #"<3F7 %
 
 
 	

 x||F##)r**0022D 
C #
 
 
 	
 2%!#%'  G w{{>266<"==CCEELOR88>B??EEGGM 
@ -
 
 
 	

  
@ -
 
 
 	
 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"  s   6C"L G. -L .G=:L <G==$L "%I L 
LB6LL LL 
N!(M
	N

MNMNM21N2
M?<N>M??Nc            
     \	   ddl } d}t          }	 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t          d|j         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|                     |	           |                    | d#||d$dd	i
          }|j        dk    r|	                                }n%|j        d%v rzt          d&|j         ddd'          ddd           n# 1 swxY w Y   n,# t           $ r t          d(           t#          d)          w xY w|t          d*dd+          |
                    d,d          }|
                    d-d          }| d.}|r|st          d/dd0          	 t          j        t          j        d                    5 }|                    t$          d,||||d1dd2i3          }ddd           n# 1 swxY w Y   n'# t          $ r}t          d4| dd5          d}~ww xY w|j        dk    rt          d6|j         ddd7          |	                                }|
                    d8d          }|
                    d9d          }|st          d:dd;          t'          j        d<d                                                              d=          pt.          }||d>|t1          j        t4          j                                                                      d?d@          dAdBdCS )DzBRun the OpenAI device code login flow and return credentials dict.r   Nzhttps://auth.openai.comr!   r  z!/api/accounts/deviceauth/usercoder9   r  r  )r  r  zFailed to request device code: rL   device_code_request_failedr  r   z$Device code request returned status r  device_code_request_errorr,  r6   device_auth_idr  r/  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.device_code_timeoutr  r  z/deviceauth/callbackzADevice auth response missing authorization_code or code_verifier.device_code_incomplete_exchanger  r  r  r  zToken exchange failed: token_exchange_failedzToken exchange returned status token_exchange_errorr   r  z.Token exchange did not return an access_token.token_exchange_no_access_tokenr  r  )r   r  r  r  r  zdevice-code)r
  r!  r  r  r  )r  r  r$  r  r  r%  r   rA  r&  r  r3  r  r   r  r  r  r6  r7  r  r   r   r   r  r  r   r  r   r  r  r  )_timeissuerr9   r  r*  r  device_datar,  r  r  max_waitr  	code_resp	poll_respr  r  ri  
token_respr
  r   r  r!  s                         rI   r  r    s   &F%I
\%-"5"5666 	&;;<<<!9-');<   D	 	 	 	 	 	 	 	 	 	 	 	 	 	 	  
 
 
3c33#*F
 
 
 	

 3F43CFFF#*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
 
 
 	

 $$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   'A.  A"A. "A&&A. )A&*A. .
B8BB='J $BJ:J J

J J
J )J;'M? &M3'M? 3M77M? :M7;M? ?
N#	NN#r?   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   Nrw  `   r  r/  )
secretstoken_urlsafer  r~  r4  r5  r6  r  r  r  )r  verifier	challenger;  s       rI   _minimax_pkce_pairr    s    NNN$$R(("-H(x(())0022 fhhvvc{{  !!"%%EY%%rH   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/coderD  r  )r  r9   r:   r  r  r;  r  r  )r  r  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_SCOPEr2   r  r  r&  rA  r  reason_phraser  r3  )r  r7   r9   r  r;  r)  r  r   s           rI   _minimax_request_user_coder    s@    {{'''#"(,%+
 
 @(
--
 
   H  s""\8=3ZHDZ\\$+A
 
 
 	
 mmooG@  @@@(/I     
 {{7u$$;$+;
 
 
 	
 NrH   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     rI   &_minimax_expired_in_looks_like_unix_msr    s    z??fk**rH   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).r  r       @@r    )r   r  r  r  )r  r  rX  r  s       rI   "_minimax_resolve_token_expiry_unixr    sa    
j//C4'((F-c&AAA V|==??SC[[((rH   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   r  r  r  r    g       @i  /oauth/token)r  r9   r,  r  r  r  r  r  r   	base_resp
status_msgzMiniMax OAuth error: r  r{   r  r  r  rP  z8MiniMax OAuth reported an error. Please try again later.authorization_deniedsuccessc              3  B   K   | ]}                     |          V  d S rJ  r^  )r   kr  s     rI   r   z&_minimax_poll_token.<locals>.<genexpr>  s-      __!w{{1~~______rH   )r   r  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&  r3  rA  allr  )r  r7   r9   r,  r  r  r  r  r  rX  r  r/  r)  r  r  r  s                  @rI   _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  r1  r8  r  )r  r:  s     rI   _minimax_save_auth_stater
     s    			 % %%''
Z*EEE$$$% % % % % % % % % % % % % % % % % %s   /AAAr~   rC  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: r  r  T)r  r  follow_redirects)r7   r9   r  r;  r-  r,  To continue:  1. Open:   2. If prompted, enter code: #  (Opened browser for verification)z<  Could not open browser automatically -- use the URL above.r/  NzWaiting for approval...r  )r7   r9   r,  r  r  r  r  r   r#  r$  r   r  r%  r  )rC  r~   r7   r8   r9   r:   r#  r   r  r%  r  r  r  u#   ✓ MiniMax OAuth login successful.notification_messagezNote from MiniMax: )r   r=   r7   r8   r  r2  r  r$  r  r  r  r9   r2   r@  r3  r  r3  r   r  r   r  r   r  r  r  r  r  r  r  r
  )r~   rC  r  r   r7   r8   r  r  r;  r  	code_dataverification_urlr,  interval_rawr  
token_datar  expires_at_unixexpires_in_sr  r  s                        rI   _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).r  z:MiniMax OAuth state has no refresh_token; please re-login.r{   no_refresh_tokenTrB  r  r6   rO  r7   )r  r  r  r9   r  r  r  r  r  Nr   c              3      K   | ]}|v V  	d S rJ  r>   )r   r_  r*  s     rI   r   z/_refresh_minimax_oauth_state.<locals>.<genexpr>  s?       Z ZAa4i Z Z Z Z Z ZrH   )r  r  invalid_refresh_tokenzMiniMax OAuth refresh failed: refresh_failedr  r  z-MiniMax OAuth refresh did not return success.r  r  r   r   r  )r   r  r  r  r  )r3  rA  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  rG   r  r  r  r
  )r;  r  r  r  r  r7   r  r)  r  r  now_dtr  r  	new_stater*  s                 @rI   _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.
    r  N)r   r  r  r  r  r{   r!  r   Tr  r  z6MiniMax OAuth: failed to persist quarantined state: %s)rE  r3  r7  rD  r2   r   r  r   r  r  r
  r   r   r'  )r;  r  _kr  s       rI   -_minimax_oauth_quarantine_on_terminal_refreshr(    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   r2   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_inTrB  z6MiniMax OAuth state has no access_token after refresh.no_access_token)r:  r3  rA  r%  r(  )r;  r  r  s      rI   _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   r2   r>   )r/  s    rI   "build_minimax_oauth_token_providerr2    s    .   * OrH   )min_token_ttl_secondsas_token_providerr3  r4  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-  TrB  Nr8   r  r  r0  )r:  r3  rA  r%  r(  r2  r  )r3  r4  r;  r  rN   s        rI   )resolve_minimax_oauth_runtime_credentialsr6    s    " $O44E 
		.11 
$?T
 
 
 	

,U33   5eSAAA  (9;;'#./66s;;	  r0  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  r6   r   r~   r}   )r  rC  r~   r  )r:  r3  r   r  r  r  r   r   )r;  r  token_valids      rI   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  rA  r  rc  r7  )r  r   r~   rC  r  r  s         rI   _login_minimax_oauthr:  @  s    T8T**6hFt\5999LdIt,,4Gg	
 	
 	
 	
 	
 	
    $$%%%mms   A 
B+BBr7   r8   r9   r:   rC  r  r  r  c           
     h	   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           t'          dt)          |t*                              }t          d| d           t-          || |t          |d                   ||          }d d d            n# 1 swxY w Y   t/          j        t2          j                  }t7          |                    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,t/          j         |t2          j        -                                          d|d.|du tC          |t                    r|nd d/d0d d1d d2d d3d d4d d5d }	 tE          ||d6          S # tF          $ r}|j$        d7k    r|                    d$tJ                                        d          }tM          |          }t                       t          |           t          d8| d9           t                       t          d:           tO          d           d }~ww xY w);zMRun the Nous device-code flow and return full OAuth state without persisting.rJ   r  r  r  r  FTzStarting Hermes login via z...r  z'TLS verification: disabled (--insecure)z$TLS verification: custom CA bundle (r}  r  r  r  )r  r7   r9   r:   r.  r,  r  r/  r  r  r  r  u=     Could not open browser automatically — use the URL above.r    z$Waiting for approval (polling every zs)...r#   )r  r7   r9   r#   r  r  Nr   r8   z%Using portal-provided inference URL: r7   r9   r:   r#  r$  r   r  r  r  r  r  r{  r  r  r  r  r  r  )r  r4  r\  z  Subscribe here: z/billingz<After subscribing, run `hermes model` again to finish setup.)(r   r   r   r7   r  r8   r9   r:   r$  r  r2  r  r4   r  r5  r2   r   r3  r  r  r;  r<  rA  r   r  r   r  r  r3  r  r  r  r  r   r  rA  rD  r^  rc  r7  )r7   r8   r9   r:   rC  r  r  r  r   requested_inference_urlr  r  r  r  r  r,  r  r/  rF  effective_intervalr  r  token_expires_inr  resolved_inference_urlr  r  rT  rF  s                                rI   _nous_device_code_loginrA  N  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 C2W$X$XYYN5GNNNOOO$+K677!"
 
 

9#
 #
 #
 #
 #
 #
 #
 #
 #
 #
 #
 #
 #
 #
 #
J ,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--s,   ,D>I66I:=I:+O= =
R1B%R,,R1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]: rM  >   r6   rM  rv  z3Rehydrating Nous session from shared credentials...r  uK   Could not refresh shared credentials — falling back to device-code login.rT  inference_urlr9   r:   r:  r;  r8   r  rJ   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_recommendationsr6   Trh  r7   re  zpaid Nous modelsrj  )	free_tierzShowing u=    curated models — use "Enter custom model name" for others.)rP  rR  rT  rU  rV  rW  rX  r  z#No free models currently available.ry  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.)r7  zDefault model set to: r  z (model.provider=nous)r  r  zLogin failed: r    ).r   r   r   r   rP  rJ  r  r  r4  r   r   r5  r6  r  rA  r9   r  r1  r3  r8  r  ra  r  r   r2   rA  r~  rD  rE  rF  rG  rH  rI  rl  rf  rg  r   r   r  r^  r  rc  r7  r?  r  r7  )$r  r   r  r  r  r  rD  shared_pathr  r8   _prior_storeprior_active_providerr:  rI  selected_modelr  rD  rE  rF  rG  rH  rI  r  rR  rU  rP  rJ  _portal_for_recsrf  rg  _account_info_portal_urlr  rF  r;  s$                                       rI   _login_nousrS    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.rC  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  r7  r=  rG  rE  r  rI  rI  r   r   )r  r   r@  rG  should_reset_configrL  s         rI   logout_commandrV    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:::::rH   r1  )rN   r2   r   r2   r   r2   r   r2   )r   r   r   r   r   r   )r   r2   r   r1   r   r   )r  )rN   r2   r  r  r   r  )rP  r   r   r   )r  r   r   rU  )rP  r   r   r2   )rP  rA  r   r2   )r  r   r   rG  r  )rz  r2   ry  rG  r{  r   r   rH  )r   r   )r   r  )r   r<   )r  r   r  r  r  r  r  r2   )r  r  rJ  )r  r  r   r<   )r:  r<   r   r   )r:  r<   r   r2   r   r  )r:  r<   r   r2   r;  r<   r   rH  )
r:  r<   r   r2   r;  r<   r  r   r   rH  )r   r2   r   rH  )r   r2   r   r   )r   r2   r   r2   )r   rG  r   r<   )r   r2   r%  r&  r   r   )r   r2   r  r2   r   rH  )r   r2   r  r2   r   r   )r   r2   r   r  )r   rG  )r   rG  r   r   r  )rL  r2   r   r2   )r]  rG  r[  rG  r\  rG  r   r2   )r   r   r   r  )r  r   r  r   r   r   )r  r   r   r   )r   r   r   rG  )r  rG  r   rG  )r  r   r   r<   )r  r   r   r  )
r  r   r:   r   r  r   r  r   r   rG  )
r  r   r:   r   r  r   r  r   r   r   )r;  r<   r   r   r   rH  )r   r   ry  rG  r   rH  )r  r   r  r   r   rG  )r;  r<   r  rG  r   rH  )r;  r<   r   r   ry  rG  r   rH  )r;  r<   r   r<   )r   r   r  r   r   r   )r
  r<   r   r   )r  r   r  r   r   r   )r  )r
  r<   r  r  r   r<   )r.  r<   r   rH  )r4  r   r5  r   r6  r   r   r<   )r4  r   r   r<   )r  rG  r   rS  )r  rG  r   r2   )NN)r]  rG  r;  r  r   r2   )r;  r  r   r2   )rw  )rx  r   r   r2   )r  r2   r   r2   )r9   r2   ri  r2   r:   r2   r;  r2   r  r2   rt  r2   r   r2   )ri  r2   r   r  )r  r2   r   r  )ri  r2   r  r  r   r  )r  rG  r   r2   )r  r   r   r  )r  r
   r  r  r  r  r  r  r   rG  r   r  )r  r<   r9   r2   ri  r2   r  r2   rt  r2   rn  r2   r  r  r   r<   )r9   r2   rD  r2   ri  r2   r  r2   rt  r2   r  r  r   r<   )r;  r<   r  r  r   r<   )r-  r2   r   r2   )rX  r2   r   rG   )ri  r2   r   rG   )ri  r2   r<  rz  r   rH  )r  r   r   r<   )
r:  r<   r
  r.   r  rG  r  r  r   rH  )r
  r.   r  r2   r"  r2   r   rH  )r   r2   r  r2   r  r  r   r<   )r
  r.   r  r  r   r.   )r   r  )
r
  r<   r  r  ri  r2   r  rG  r   rH  )r   )r   r2   r  r   r   r   )r  r2   r   r2   r   r2   )r   r2   r  r2   r   r2   )r!   )r  r  r   r.   )
r   r2   r  r2   r  r2   r  r  r   r<   )
r
  r<   r  r2   ri  r2   r  r  r   r<   )r   r  )r  r  r  rG  r  r  r   r  )
r  r)  r7   r2   r9   r2   r:   rG  r   r<   )r  r)  r7   r2   r9   r2   r#   r2   r  r   r  r   r   r<   )r;  r<   r   r   )r;  r<   r   rH  )r   r  r  )r  r   r   r   )r;  r<   rP  rA  r  r2   r   rH  )r:  r<   rP  rA  r  r2   r   r   )r  r  r   r  )
r  r)  r7   r2   r9   r2   r  r2   r   r<   )
r8   r2   rN   r2   r  r  r  r  r   rS  )r;  r<   r  r   r   r   )
r  r  r  r  r  rG  r6  r   r   r2   )"r   r2   r  r2   r9   r2   r7   r2   r8   r2   r#  r2   r:   r2   r  rG  r  rG  r  rG  r  rG  r  r  r  r  r  rG  r4  r   r  r  r   r<   )
r;  r<   r  r  r4  r   r  r  r   r<   )r.  r<   r"  rG  )
r  r  r  r  r  rG  r4  r   r   r<   )r   r  )r   r2   r   r<   )r   r2   r8   r2   r7  rG  r   r   )
r  r2   rC  r2   r!  r2   rN   r2   r   r   )r6   NNr6   r6   r6   r6   r6   )r  rS  r=  r2   rP  rQ  rR  rS  rT  r2   rU  r2   rV  r2   rW  r2   rX  r2   r   rG  )r  r2   r   rH  )r   r1   r  r   r   rH  )r  r2   ri  r2   r  r2   r;  r2   r  r2   r   r2   )r  r2   rD  r2   ri  r2   r  r2   r  r2   r  r  r   r<   )r  r  rC  r   r  r   r   r<   )r   r?   )r  r)  r7   r2   r9   r2   r  r2   r;  r2   r   r<   )r  r   r  r   r   r   )r  r   r  r   r   r  )r  r)  r7   r2   r9   r2   r,  r2   r  r2   r  r   r  rU  r   r<   )r  r<   r   rH  )r~   r2   rC  r   r  r  r   r<   )r;  r<   r  r  r  r   r   r<   )r;  r<   r  rA  r   rH  )r   r)  )r3  r   r4  r   r   r<   )r   r1   r   rH  )r7   rG  r8   rG  r9   rG  r:   rG  rC  r   r  r  r  r   r  rG  r   r<   (U  rE   
__future__r   r  loggingr   r  r  r  r  r  r  r4  r  r  r  r  r3  
contextlibr   dataclassesr   r   r   r   http.serverr	   r
   r   pathlibr   typingr   r   r   r   r   r   r   urllib.parser   r   r   r$  r   r   r   r   r  r   r   agent.credential_persistencer   utilsr   r   r   	getLoggerrB   r   r  r   r  r  r  r^  r_  r]  r  r\  ru  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"  r;  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  'CODEX_ACCESS_TOKEN_REFRESH_SKEW_SECONDSXAI_OAUTH_ISSUERr  r  r  r  r  r  %XAI_ACCESS_TOKEN_REFRESH_SKEW_SECONDSr(  r'  &QWEN_ACCESS_TOKEN_REFRESH_SKEW_SECONDSru  ro  rj  r0  r1  )SPOTIFY_ACCESS_TOKEN_REFRESH_SKEW_SECONDSr  r}  rT  rV  r/   rF   rN  .GEMINI_OAUTH_ACCESS_TOKEN_REFRESH_SKEW_SECONDSr1  r1   r   r  r   _list_providers_for_registryr  r4   r5   env_varsr?   _api_key_varsr  _base_url_vardisplay_namer!  r  r  r   r   r   r   r   r	  r#  r+  r>  rR  r  rA  rT  rZ  rc  rb  rq  rx  r  r  r  r  r  localr  r  r  r1  r  r2  r8  r  r  r  r  r$  r+  r2  r4  r8  r:  r=  rE  rI  rK  rZ  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-  r2  r=  r@  rC  rP  rR  rZ  r\  re  rk  rq  rv  r  r  r  r  r  r  r  r  r  r  r  r  r  r	  r  r   r%  r)  r,  r8  rJ  r2  r[  r@  r
  rq  ry  r?  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r(  r5  rA  rH  rL  rF  rJ  rM  rW  ra  rP  rg  rl  r  ro  rr  ry  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  rq  r  r  r  r  r  r  r  r  r2  r6  r?  rA  rC  rE  rG  rI  rO  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r
  r  r%  r(  r2  r6  r  r:  rA  rS  rV  r>   rH   rI   <module>rz     s!    $ # " " " " "   				   



  



                 % % % % % % ( ( ( ( ( ( ( ( ' ' ' ' ' ' ' ' O O O O O O O O O O       H H H H H H H H H H H H H H H H H H 6 6 6 6 6 6 6 6 6 6  O O O O O O O O O O C C C C C C C C M M M M M M D D D D D D D D D D		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%  % (+ %9 A ), &$B !; H [ A ,/ )W \  "    y*     
 %< !13 . 1          S0
NN%/5(   S0 NN"1	  S0  ..6,*  !S00 4"5	  1S0< .."0	  =S0H $"<	  IS0T 5(&  US0d ~~9M/  eS0t >>!$7/  uS0B nnM=*  CS0R 
>>9G'  SS0b >> 8@(
 
 
cS0x nn&7-  yS0F ~~ :-+  GS0V ^^8-)  WS0f 
>>;)'  gS0v ~~=-+  wS0 S0F ^^0!19)!!9N(BD D
 
 
GS0\ 6\-  ]S0l ~~S/-  mS0| >> *JM7  }S0L ..?0.  MS0\ 8.,  ]S0l 
>>0)'  mS0| nn@,*  }S0L NN720  MS0\ >> ;1/  ]S0v <.,  wS0F >>=&&  GS0V nn:,*  WS0f @.,  gS0v NN8,*  wS0F ~~L+  GS0V ^^31  WS0 S0  S S S Sp	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	   @ 3    "     89 	 	 	 	 	 	& & & &l 4gY(S	<wi7S;>d>d>df|}CEkEkEk  nC  D# # # # #L. . . .p / 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     $%< %< %< %< %<P. . . .b   :0 0 0 0 4 4 4 4 4 4 ) ) ) )$S S S S
? ? ? ?,L ,L ,L ,L ,L^, , , ,*% % % %      ,9 9 9 9"- - - -0 0 0 0f% % % % %P	% 	% 	% 	%"   4  $Q '+'+	Q Q Q Q Q Qp   "9 9 9 9   ( ( ( ( 1:	$; 1 1     
, , , ,^
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	     @
 
 
 
>% % % %(  ' ' ' ' ' 'T   6	 	 	 	 	4 4 4 4 4
 #&*    0 #&*( ( ( ( ((( ( ( ( (- - - - -! ! ! ! !
H H H H
! ! ! ! !
H H H H
4 4 4 4*1 1 1 10#+ #+ #+ #+R #$ $ $ $ $ $N1 1 1 10/ / / /o' o' o' o'f 2%0 %0 %0 %0 %0Z #/3+ + + + + +\   . 04! ! ! ! ! !V ", , , , , ,d "= = = = = =D   $ I	- - - - - -`   &: : : :z\) \) \) \)D   J *3  
* 
*  
 
 
 
6 6 6 6r/ / / /d' ' ' '6        KO + + + + + +l )- . . . . . .j ;?	b, b, b, b, b,J% % % % %@ "	} } } } } }@   *   H   $ G	F F F F F FR% % % %X -1 + + + + + +b +/"&% % % % % %.    $& & & &R5 5 5 5p. . . . .j !_ _ _ _ _ _L 	     B   $ E	O O O O O Ol   (  $#+/	           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f) f) f) f) f) f)` #!% $#*.!##GK#^ ^ ^ ^ ^ ^H "GK     @  8 8 8 8 8 8vU U U U "##b b b b b bR
 
 
 
8) 8) 8) 8)F # SW  W W W W   	# 	# 	# 	#   @1( 1( 1( 1(h.
 .
 .
 .
b'
 '
 '
 '
T   >   <"  "  "  "  " JJ J J JZ* * * *Z% % % %^ $(? ? ? ? ?D   $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\ "	9I 9I 9I 9I 9I 9IxE E E EH "u u u u u ut "	I I I I I IXN N N Nf& & & &% % % %P+ + + +
) ) ) )< < < <~% % % % D!P P P P P Ph 8<? ? ? ? ? ?DZ Z Z Z2, , , ,` %G#& & & & & &R   $     &*(,#!#~ ~ ~ ~ ~ ~BI I I IX; ; ; ; ; ;s7   ?C CCC C! C!/CR5 5R=<R=