
    iv                       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mZ ddlmZ ddlmZmZmZmZ ddlmZ  e	j        e          Z dZ!d	Z"d
Z#dZ$dZ%e# de$ dZ&de% Z'ddl(Z)ddl*m+Z+  e)j,        d          Z- e)j,        d          Z. e)j,        d          Z/ e)j,        d          Z0dZ1dZ2dZ3dZ4dZ5dZ6dZ7dZ8dZ9dZ:dZ;d Z< G d! d"e=          Z>dsd%Z?dsd&Z@ ejA                    ZBejC        e;fdtd)            ZDi ZEd*eFd+<   dud-ZGdvd/ZHdwd1ZIdwd2ZJdwd3ZKdvd4ZLe G d5 d6                      ZMe G d7 d8                      ZNdxd:ZOdyd<ZPdzd>ZQd{dCZRdde9dDd|dKZSdde9dDd}dMZTe9fd~dOZUi ZVdPeFdQ<    ejW                    ZXdRdSddVZYdddZZZ G d[ d\ej[        j\                  Z]d]Z^d^Z_e5fddbZ`ddcZadRdde:dWdeddiZbddlZcddmZddWdnddpZeddqZfdwdrZgdS )ux  Google OAuth PKCE flow for the Gemini (google-gemini-cli) inference provider.

This module implements Authorization Code + PKCE (S256) OAuth against Google's
accounts.google.com endpoints. The resulting access token is used by
``agent.gemini_cloudcode_adapter`` to talk to ``cloudcode-pa.googleapis.com``
(Google's Code Assist backend that powers the Gemini CLI's free and paid tiers).

Synthesized from:
- jenslys/opencode-gemini-auth (MIT) — overall flow shape, public OAuth creds, request format
- clawdbot/extensions/google/ — refresh-token rotation, VPC-SC handling reference
- PRs #10176 (@sliverp) and #10779 (@newarthur) — PKCE module structure, cross-process lock

Storage (``~/.hermes/auth/google_oauth.json``, chmod 0o600):

    {
      "refresh": "refreshToken|projectId|managedProjectId",
      "access": "...",
      "expires": 1744848000000,   // unix MILLIseconds
      "email": "user@example.com"
    }

The ``refresh`` field packs the refresh_token together with the resolved GCP
project IDs so subsequent sessions don't need to re-discover the project.
This matches opencode-gemini-auth's storage contract exactly.

The packed format stays parseable even if no project IDs are present — just
a bare refresh_token is treated as "packed with empty IDs".

Public client credentials
-------------------------
The client_id and client_secret below are Google's PUBLIC desktop OAuth client
for their own open-source gemini-cli. They are baked into every copy of the
gemini-cli npm package and are NOT confidential — desktop OAuth clients have
no secret-keeping requirement (PKCE provides the security). Shipping them here
is consistent with opencode-gemini-auth and the official Google gemini-cli.

Policy note: Google considers using this OAuth client with third-party software
a policy violation. Users see an upfront warning with ``confirm(default=False)``
before authorization begins.
    )annotationsN)	dataclass)Path)AnyDictOptionalTupleget_hermes_homeHERMES_GEMINI_CLIENT_IDHERMES_GEMINI_CLIENT_SECRET681255809395 oo8ft2oprdrnp9e3aqf6av3hmdib135jz4uHgMPm-1o7Sk-geV6Cu5clXFsxl-z.apps.googleusercontent.comzGOCSPX-)atomic_replacezPOAUTH_CLIENT_ID\s*=\s*['\"]([0-9]+-[a-z0-9]+\.apps\.googleusercontent\.com)['\"]z;OAUTH_CLIENT_SECRET\s*=\s*['\"](GOCSPX-[A-Za-z0-9_-]+)['\"]z7([0-9]{8,}-[a-z0-9]{20,}\.apps\.googleusercontent\.com)z(GOCSPX-[A-Za-z0-9_-]{20,})z,https://accounts.google.com/o/oauth2/v2/authz#https://oauth2.googleapis.com/tokenz-https://www.googleapis.com/oauth2/v1/userinfozhttps://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profilei  z	127.0.0.1z/oauth2callback<   g      4@i,  g      >@)SSH_CONNECTION
SSH_CLIENTSSH_TTYHERMES_HEADLESSc                  *     e Zd ZdZddd
 fd	Z xZS )GoogleOAuthErrorz0Raised for any failure in the Google OAuth flow.google_oauth_errorcodemessagestrr   returnNonec               X    t                                          |           || _        d S N)super__init__r   )selfr   r   	__class__s      7/home/ubuntu/.hermes/hermes-agent/agent/google_oauth.pyr#   zGoogleOAuthError.__init__   s&    !!!			    )r   r   r   r   r   r   )__name__
__module____qualname____doc__r#   __classcell__)r%   s   @r&   r   r      sP        ::4H            r'   r   r   r   c                 *    t                      dz  dz  S )Nauthzgoogle_oauth.jsonr
    r'   r&   _credentials_pathr0      s    v%(;;;r'   c                 D    t                                          d          S )Nz
.json.lock)r0   with_suffixr/   r'   r&   
_lock_pathr3      s    **<888r'   timeout_secondsfloatc              #    K   t          t          dd          }|dk    rF|dz   t          _        	 dV  t          xj        dz  c_        n# t          xj        dz  c_        w xY wdS t                      }|j                            dd           t          j        t          |          t          j	        t          j
        z  d          }d}	 	 ddl}n# t          $ r d}Y nw xY w|t          j                    t          d	t!          |                     z   }	 	 |                    ||j        |j        z             d}nO# t(          $ rA t          j                    |k    rt+          d
| d          t          j        d           Y nw xY wvn	 ddl}t          j                    t          d	t!          |                     z   }	 	 |                    ||j        d           d}nO# t4          $ rA t          j                    |k    rt+          d
| d          t          j        d           Y nw xY won# t          $ r d}Y nw xY wdt          _        dV  	 |ru	 ddl}|                    ||j                   nT# t          $ rG 	 ddl}	 |                    ||j        d           n# t4          $ r Y nw xY wn# t          $ r Y nw xY wY nw xY wt          j        |           dt          _        dS # t          j        |           dt          _        w xY w# 	 |ru	 ddl}|                    ||j                   nT# t          $ rG 	 ddl}	 |                    ||j        d           n# t4          $ r Y nw xY wn# t          $ r Y nw xY wY nw xY wt          j        |           dt          _        n%# t          j        |           dt          _        w xY ww xY w)zNCross-process lock around the credentials file (fcntl POSIX / msvcrt Windows).depthr      NTparentsexist_oki  Fg        z5Timed out acquiring Google OAuth credentials lock at .g?)getattr_lock_stater7   r3   parentmkdirosopenr   O_CREATO_RDWRfcntlImportErrortime	monotonicmaxr5   flockLOCK_EXLOCK_NBBlockingIOErrorTimeoutErrorsleepmsvcrtlockingLK_NBLCKOSErrorLOCK_UNLK_UNLCKclose)r4   r7   lock_file_pathfdacquiredrE   deadlinerP   s           r&   _credentials_lockr[      s      K!,,Eqyy!AI	#EEE"K"\\Nt<<<	^$$bj29&<e	D	DBH;"	LLLL 	 	 	EEE	 ~''#c53I3I*J*JJH
%	%KKEMEM$ABBB#H& % % %~''833*eTbeee   Jt$$$$$%
%  >++c#u_7M7M.N.NN
)	)r6?A>>>#'" ) ) )>++x77". iXf i i i# #  
4((((()
)          	"  LLLKKEM2222" 	 	 	%!"NN2vBBBB& ! ! ! D!&   	 HRLLL !K HRLLL !K!!!!%	"  LLLKKEM2222" 	 	 	%!"NN2vBBBB& ! ! ! D!&   	 HRLLL !K HRLLL !K!!!!!!!!s  A	 	A C L C!L  C!!7L %D? >L ?AF
L 	F

L 6H6 G& %H6 &AH1.H6 0H11H6 5L 6IL IL K2 I< ;K2 <
KJ:J)(J:)
J63J:5J66J:9K:
KKKK
K2 KK2 2"LON1L<;N1<
NM:M)(M:)
M6	3M:5M6	6M:9N:
N	NN	N
N1NN1!O1"OODict[str, str]_scraped_creds_cacheOptional[Path]c                    ddl } |                     d          }|sdS 	 t          |                                          }n# t          $ r Y dS w xY wg }|j        }t          d          D ]b}|                    |           |dz                                  r |                    |dz  dz  dz              n|j        |k    r n|j        }c|D ]}|                                s|dz  d	z  d
z  dz  |dz  d
z  dz  |d	z  d
z  dz  g}|D ]}|                                r|c c S 	 |	                    d          D ]}	|	c c S x# t          t          f$ r Y w xY wdS )a  Walk the user's gemini binary install to find its oauth2.js.

    Returns None if gemini isn't installed. Supports both the npm install
    (``node_modules/@google/gemini-cli-core/dist/**/code_assist/oauth2.js``)
    and the Homebrew ``bundle/`` layout.
    r   Ngemini   node_modulesz@googlezgemini-cli-coredistsrccode_assistz	oauth2.js)shutilwhichr   resolverS   r?   rangeappendexistsrglob
ValueError)
rf   r`   realsearch_dirscur_root
candidatescpaths
             r&   _locate_gemini_cli_oauth_jsrv      s    MMM\\(##F tF||##%%   tt !K
+C1XX  3. ((** 	s^3i?BSSTTTE:Ej  {{}} 	 6ME!M1K?6MM)K75L=(;6


  	 	Axxzz 	

;//  $ 	 	 	H	 4s#   !A 
AA)EEETuple[str, str]c                 :   t                               d          r6t                               dd          t                               dd          fS t                      } | dt           d<   dS 	 |                     dd	
          }n># t          $ r1}t
                              d| |           dt           d<   Y d}~dS d}~ww xY wt                              |          pt                              |          }t                              |          pt                              |          }|r|                    d          nd}|r|                    d          nd}|t           d<   |t           d<   dt           d<   |rt
                              d|            ||fS )zDExtract client_id + client_secret from the local gemini-cli install.resolved	client_id client_secretN1r{   r{   utf-8replace)encodingerrorsz"Failed to read oauth2.js at %s: %sr8   z#Scraped Gemini OAuth client from %s)r]   getrv   	read_textrS   loggerdebug_CLIENT_ID_PATTERNsearch_CLIENT_ID_SHAPE_CLIENT_SECRET_PATTERN_CLIENT_SECRET_SHAPEgroupinfo)oauth_jscontentexc	cid_matchcs_matchrz   r|   s          r&   _scrape_client_credentialsr   0  s   
++ h#''R88:N:R:RSbdf:g:ggg*,,H+.Z(v$$gi$HH   98SIII+.Z(vvvvv #))'22V6F6M6Mg6V6VI%,,W55]9M9T9TU\9]9]H&/7	"""RI)19HNN1%%%rM(1%,9)'*$ E98DDDm##s   .B 
C&B<<Cr   c                     t          j        t                    pd                                } | r| S t          rt          S t                      \  }}|S Nr{   )rA   getenvENV_CLIENT_IDstrip_DEFAULT_CLIENT_IDr   )env_valscrapedrq   s      r&   _get_client_idr   R  sQ    y''-24466G  "!!+--JGQNr'   c                     t          j        t                    pd                                } | r| S t          rt          S t                      \  }}|S r   )rA   r   ENV_CLIENT_SECRETr   _DEFAULT_CLIENT_SECRETr   )r   rq   r   s      r&   _get_client_secretr   \  sR    y*++1r88::G  &%%+--JAwNr'   c                 H    t                      } | st          dd          | S )Na  Google OAuth client ID is not available.
Hermes looks for a locally installed gemini-cli to source the OAuth client. Either:
  1. Install it: npm install -g @google/gemini-cli  (or brew install gemini-cli)
  2. Set HERMES_GEMINI_CLIENT_ID and HERMES_GEMINI_CLIENT_SECRET in ~/.hermes/.env

Register a Desktop OAuth client at:
  https://console.cloud.google.com/apis/credentials
(enable the Generative Language API on the project).google_oauth_client_id_missingr   )r   r   )cids    r&   _require_client_idr   f  s>    


C 
C 2
 
 
 	
 Jr'   c                    t          j        d          } t          j        |                     d                                                    }t          j        |                              d          	                    d          }| |fS )z1Generate a (verifier, challenge) pair using S256.@   ascii   =)
secretstoken_urlsafehashlibsha256encodedigestbase64urlsafe_b64encoderstripdecode)verifierr   	challenges      r&   _generate_pkce_pairr   |  sp    $R((H^HOOG4455<<>>F(0077==DDWMMIYr'   c                  V    e Zd ZU ded<   dZded<   dZded<   edd	            Zdd
ZdS )RefreshPartsr   refresh_tokenr{   
project_idmanaged_project_idpackedr   'RefreshParts'c                    |s | d          S |                     dd          } | |d         t          |          dk    r|d         ndt          |          dk    r|d         nd          S )Nr{   )r   |   r   r8   r   r   r   )splitlen)clsr   partss      r&   parsezRefreshParts.parse  s     	)3R((((S!$$s(#&u::>>uQxxr+.u::>>uQxxr
 
 
 	
r'   c                p    | j         sdS | j        s| j        s| j         S | j          d| j         d| j         S )Nr{   r   r   r$   s    r&   formatzRefreshParts.format  sS    ! 	2 	&t'> 	&%%$RRtRR9PRRRr'   N)r   r   r   r   r   r   )	r(   r)   r*   __annotations__r   r   classmethodr   r   r/   r'   r&   r   r     s}         J     
 
 
 [
S S S S S Sr'   r   c                      e Zd ZU 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dZedd            Z	ddZ
efddZdS )GoogleCredentialsr   access_tokenr   int
expires_msr{   emailr   r   r   Dict[str, Any]c                    t          | j        | j        | j                                                  | j        t          | j                  | j        dS )Nr   )refreshaccessexpiresr   )	r   r   r   r   r   r   r   r   r   r   s    r&   to_dictzGoogleCredentials.to_dict  sW    #"0?#'#:   fhh'4?++Z	
 	
 		
r'   data'GoogleCredentials'c                   t          |                    dd          pd          }t                              |          } | t          |                    dd          pd          |j        t          |                    dd          pd          t          |                    dd          pd          |j        |j                  S )Nr   r{   r   r   r   r   r   r   r   r   r   r   )r   r   r   r   r   r   r   r   )r   r   refresh_packedr   s       r&   	from_dictzGoogleCredentials.from_dict  s    TXXi44:;;"">22sTXXh339r::-488Iq116Q77dhhw++1r22'$7
 
 
 	
r'   r5   c                    | j         dz  S )Ng     @@)r   r   s    r&   expires_unix_secondsz&GoogleCredentials.expires_unix_seconds  s    ''r'   skew_secondsboolc                    | j         r| j        sdS t          j                    t          d|          z   dz  | j        k    S )NTr     )r   r   rG   rI   )r$   r   s     r&   access_token_expiredz&GoogleCredentials.access_token_expired  sB      	 	4	c!\222d:doMMr'   Nr   r   )r   r   r   r   )r   r5   )r   r   r   r   )r(   r)   r*   r   r   r   r   r   r   r   r   REFRESH_SKEW_SECONDSr   r/   r'   r&   r   r     s         OOOEOOOOJ     

 

 

 

 

 

 

 [

( ( ( ( 8L N N N N N N Nr'   r   Optional[GoogleCredentials]c                    t                      } |                                 sdS 	 t                      5  |                     d          }ddd           n# 1 swxY w Y   t	          j        |          }nF# t          j        t          t          f$ r'}t          
                    d| |           Y d}~dS d}~ww xY wt          |t                    sdS t                              |          }|j        sdS |S )z?Load credentials from disk. Returns None if missing or corrupt.Nr   r   z1Failed to read Google OAuth credentials at %s: %s)r0   rk   r[   r   jsonloadsJSONDecodeErrorrS   IOErrorr   warning
isinstancedictr   r   r   )ru   rawr   r   credss        r&   load_credentialsr     s9   D;;== t   	3 	3..'.22C	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3z# '73   JDRUVVVttttt dD!! t''--E tLs:   A7 AA7 AA7 AA7 7B:B55B:r   c           	        t                      }|j                            dd           t          j        |                                 dd          dz   }t                      5  |                    dt          j	                     dt          j        d                     }	 t          |d	d
          5 }|                    |           |                                 t          j        |                                           ddd           n# 1 swxY w Y   t          j        |t$          j        t$          j        z             t+          ||           	 |                                r|                                 nO# t0          $ r Y nCw xY w# 	 |                                r|                                 w w # t0          $ r Y w w xY wxY wddd           n# 1 swxY w Y   |S )z6Atomically write creds to disk with 0o600 permissions.Tr9   r   )indent	sort_keys
z.tmp.r<      wr   r   N)r0   r?   r@   r   dumpsr   r[   r2   rA   getpidr   	token_hexrB   writeflushfsyncfilenochmodstatS_IRUSRS_IWUSRr   rk   unlinkrS   )r   ru   payloadtmp_pathfhs        r&   save_credentialsr	    sS   DKdT222jdCCCdJG			  ##$PBIKK$P$P':KA:N:N$P$PQQ	hg666 &"!!!


%%%& & & & & & & & & & & & & & & HXt|dl:;;;8T***??$$ &OO%%%   ??$$ &OO%%%%&                  Ks   %?G%F7ADFD	FD	?F(FG
FGFGG(G >G 
G	
GG	GGG#&G#r   c                    t                      } t                      5  	 |                                  n># t          $ r Y n2t          $ r&}t
                              d| |           Y d}~nd}~ww xY wddd           dS # 1 swxY w Y   dS )z"Remove the creds file. Idempotent.z3Failed to remove Google OAuth credentials at %s: %sN)r0   r[   r  FileNotFoundErrorrS   r   r   )ru   r   s     r&   clear_credentialsr     s   D			 ] ]	]KKMMMM  	 	 	D 	] 	] 	]NNPRVX[\\\\\\\\	]] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ]sB   A?4A?
A/A? 	A/	A*%A?*A//A??BBurlr   timeoutr   c                *   t           j                            |                              d          }t           j                            | |dddd          }	 t           j                            ||          5 }|                                                    dd	
          }t          j
        |          cddd           S # 1 swxY w Y   dS # t           j        j        $ r}d}	 |                                                    dd	
          }n# t          $ r Y nw xY wd}	d|                                v rd}	t          d|j         d|p|j         |	          |d}~wt           j        j        $ r}t          d| d          |d}~ww xY w)z;POST x-www-form-urlencoded and return parsed JSON response.r   POSTz!application/x-www-form-urlencodedzapplication/json)Content-TypeAccept)r   methodheadersr  r   r   r   Nr{   google_oauth_token_http_errorinvalid_grantgoogle_oauth_invalid_grantz*Google OAuth token endpoint returned HTTP z: r   z#Google OAuth token request failed:  google_oauth_token_network_error)urllibr   	urlencoder   requestRequesturlopenreadr   r   r   error	HTTPError	Exceptionlowerr   r   reasonURLError)
r  r   r  bodyr  responser   r   detailr   s
             r&   
_post_formr*    s   <!!$''..w77Dn$$?(
 
	 %  G^##GW#== 	#--//(((CCC:c??	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# <!   	XXZZ&&wy&AAFF 	 	 	D	 /fllnn,,/D[[[VEYsz[[
 
 
 	 <    7#773
 
 
 	sm   !C ;=C8C C		C C	C F&E!))DE!
D E!D  AE!!F8FF)rz   r|   r  r   r   redirect_urirz   Optional[str]r|   c                   ||nt                      }||nt                      }d| |||d}|r||d<   t          t          ||          S )z8Exchange authorization code for access + refresh tokens.Nauthorization_code)
grant_typer   code_verifierrz   r+  r|   )r   r   r*  TOKEN_ENDPOINT)	r   r   r+  rz   r|   r  r   csecretr   s	            r&   exchange_coder3  5  sn     !,)).2B2BC,8mm>P>R>RG*!$ D  ( '_ndG444r'   r   c                   | st          dd          ||nt                      }||nt                      }d| |d}|r||d<   t          t          ||          S )zRefresh the access token.z;Cannot refresh: refresh_token is empty. Re-run OAuth login."google_oauth_refresh_token_missingr   Nr   )r/  r   rz   r|   )r   r   r   r*  r1  )r   rz   r|   r  r   r2  r   s          r&   refresh_access_tokenr6  M  s      
I5
 
 
 	
 !,)).2B2BC,8mm>P>R>RG%& D
  ( '_ndG444r'   r   c                   	 t           j                            t          dz   dd|  i          }t           j                            ||          5 }|                                                    dd          }d	d	d	           n# 1 swxY w Y   t          j        |          }t          |
                    d
d          pd          S # t          $ r&}t                              d|           Y d	}~dS d	}~ww xY w)zEBest-effort userinfo fetch for display. Failures return empty string.z	?alt=jsonAuthorizationzBearer )r  r  r   r   r  Nr   r{   z%Userinfo fetch failed (non-fatal): %s)r  r  r  USERINFO_ENDPOINTr  r   r   r   r   r   r   r#  r   r   )r   r  r  r(  r   r   r   s          r&   _fetch_user_emailr:  f  sJ   .((+$&>&>&>? ) 
 
 ^##GW#== 	D--//(((CCC	D 	D 	D 	D 	D 	D 	D 	D 	D 	D 	D 	D 	D 	D 	Dz#488GR((.B///   <cBBBrrrrrs<   AC *B;C BC B;C 
C;C66C;zDict[str, threading.Event]_refresh_inflightF)force_refreshr<  r   c                   t                      }|t          dd          | s|                                s|j        S |j        }t
          5  t                              |          }| t          j	                    }|t          |<   d}nd}ddd           n# 1 swxY w Y   |sF|
                    t                     t                      }||                                s|j        S 	 	 t          |          }nS# t          $ rF}|j        dk    r5t                              d	t!                                 t#                        d}~ww xY wt%          |                    d
d          pd                                          }|st          dd          t%          |                    dd          pd                                          p|j        }	t)          |                    dd          pd          }
||_        |	|_        t)          t+          j                    t-          d|
          z   dz            |_        t1          |           |j        |rOt
          5  t                              |d           ddd           n# 1 swxY w Y   |                                 S S # |rOt
          5  t                              |d           ddd           n# 1 swxY w Y   |                                 w w xY w)a!  Load creds, refreshing if near expiry, and return a valid bearer token.

    Dedupes concurrent refreshes by refresh_token. On ``invalid_grant``, the
    credential file is wiped and a ``google_oauth_invalid_grant`` error is raised
    (caller is expected to trigger a re-login flow).
    NzYNo Google OAuth credentials found. Run `hermes login --provider google-gemini-cli` first.google_oauth_not_logged_inr   TFr  r  uh   Google OAuth refresh token invalid (revoked/expired). Clearing credentials at %s — user must re-login.r   r{   z1Refresh response did not include an access_token.google_oauth_refresh_emptyr   
expires_inr   r   r   )r   r   r   r   r   _refresh_inflight_lockr;  r   	threadingEventwaitLOCK_TIMEOUT_SECONDSr6  r   r   r   r0   r  r   r   r   rG   rI   r   r	  popset)r<  r   rteventownerfreshrespr   
new_accessnew_refreshr@  s              r&   get_valid_access_tokenrO  ~  s    E}g-
 
 
 	

  "!;!;!=!= "!! 
	B	  !%%b))=O%%E$)b!EEE                &

/
000 ""U%?%?%A%A%% 
	'++DD 	 	 	x777I%''  
 "###	 ."55;<<BBDD
 	"C1   
 $((?B77=2>>DDFF]%J],227a88
')	c"j.A.A ATIJJ! 	' 0 0!%%b$///0 0 0 0 0 0 0 0 0 0 0 0 0 0 0IIKKKK	5 	' 0 0!%%b$///0 0 0 0 0 0 0 0 0 0 0 0 0 0 0IIKKKK	st   ?BBB-C= <J+ =
EAEEDJ+ "J

J	J	+
K>5KK>K!!K>$K!%K>r{   r   r   c                l    t                      }|dS | r| |_        |r||_        t          |           dS )zFPersist resolved/discovered project IDs back into the credential file.N)r   r   r   r	  )r   r   r   s      r&   update_project_idsrQ    sK    E} &% 6#5 Ur'   c                  `    e Zd ZU dZded<   dZded<   dZded<   dZded	<   ddZddZ	ddZ
dS )_OAuthCallbackHandlerr{   r   expected_stateNr,  captured_codecaptured_errorzOptional[threading.Event]readyr   argsr   r   r   c                0    t          j        d|z   g|R   d S )NzOAuth callback: )r   r   )r$   r   rX  s      r&   log_messagez!_OAuthCallbackHandler.log_message  s&    '&084888888r'   c                   t           j                            | j                  }|j        t          k    r+|                     d           |                                  d S t           j                            |j                  }|	                    d          pdgd         }|	                    d          pdgd         }|	                    d          pdgd         }|t          |           j        k    rEdt          |           _        |                     dt                              d	
                     n	|r|t          |           _        t!          |                              dd                              dd                              dd          }|                     dt                              d| 
                     nu|r0|t          |           _        |                     dt&                     nCdt          |           _        |                     dt                              d
                     t          |           j        (t          |           j                                         d S d S )Ni  stater{   r   r!  r   state_mismatchi  u'   State mismatch — aborting for safety.)r   &z&amp;<z&lt;>z&gt;zAuthorization denied:    no_codez(Callback received no authorization code.)r  r   urlparseru   CALLBACK_PATHsend_responseend_headersparse_qsqueryr   typerT  rV  _respond_html_ERROR_PAGEr   r   r   rU  _SUCCESS_PAGErW  rG  )r$   parsedparamsr\  r!  r   safe_errs          r&   do_GETz_OAuthCallbackHandler.do_GET  s4   &&ty11;-''s###F&&v|44G$$,a0G$$,a0

6""*rdA.DJJ---(8DJJ%sK$6$6?h$6$i$ijjjj 	l(-DJJ% E

g&&f%%f%%	  sK$6$6?bX`?b?b$6$c$cdddd 	l'+DJJ$sM2222(1DJJ%sK$6$6?i$6$j$jkkk::'JJ  """"" ('r'   statusr   r'  c                B   |                     d          }|                     |           |                     dd           |                     dt          t	          |                               |                                  | j                            |           d S )Nr   r  ztext/html; charset=utf-8zContent-Length)r   re  send_headerr   r   rf  wfiler   )r$   rq  r'  r  s       r&   rj  z#_OAuthCallbackHandler._respond_html  s    ++g&&6""")CDDD)3s7||+<+<===
!!!!!r'   )r   r   rX  r   r   r   r   r   )rq  r   r'  r   r   r   )r(   r)   r*   rT  r   rU  rV  rW  rZ  rp  rj  r/   r'   r&   rS  rS    s         N#'M''''$(N(((('+E++++9 9 9 9!# !# !# !#F" " " " " "r'   rS  uz  <!doctype html>
<html><head><meta charset="utf-8"><title>Hermes — signed in</title>
<style>
body { font: 16px/1.5 system-ui, sans-serif; margin: 10vh auto; max-width: 32rem; text-align: center; color: #222; }
h1 { color: #1a7f37; } p { color: #555; }
</style></head>
<body><h1>Signed in to Google.</h1>
<p>You can close this tab and return to your terminal.</p></body></html>
u  <!doctype html>
<html><head><meta charset="utf-8"><title>Hermes — sign-in failed</title>
<style>
body {{ font: 16px/1.5 system-ui, sans-serif; margin: 10vh auto; max-width: 32rem; text-align: center; color: #222; }}
h1 {{ color: #b42318; }} p {{ color: #555; }}
</style></head>
<body><h1>Sign-in failed</h1><p>{message}</p>
<p>Return to your terminal — Hermes will walk you through a manual paste fallback.</p></body></html>
preferred_portr   "Tuple[http.server.HTTPServer, int]c                @   	 t           j                            t          | ft                    }|| fS # t
          $ r&}t                              d| |           Y d }~nd }~ww xY wt           j                            t          dft                    }||j        d         fS )NzLPreferred OAuth callback port %d unavailable (%s); requesting ephemeral portr   r8   )	httpserver
HTTPServerREDIRECT_HOSTrS  rS   r   r   server_address)rv  rz  r   s      r&   _bind_callback_serverr~  #  s    
''(GI^__~%% 
 
 
ZC	
 	
 	
 	
 	
 	
 	
 	


 [##]A$68MNNF6(+++s   /2 
A"AA"c                 >    t          d t          D                       S )Nc              3  >   K   | ]}t          j        |          V  d S r!   )rA   r   ).0ks     r&   	<genexpr>z_is_headless.<locals>.<genexpr>1  s*      88ry||888888r'   )any_HEADLESS_ENV_VARSr/   r'   r&   _is_headlessr  0  s     88%7888888r'   T)force_reloginopen_browsercallback_wait_secondsr   r  r  r  c           
     "   | s3t                      }|r#|j        rt                              d           |S t	                      }t                      }t                      \  }}t          j        d          }	t                      r0|r.t                              d           t          |||	|||          S t          t                    \  }
}dt           d| t           }|	t          _        dt          _        dt          _        t'          j                    }|t          _        ||dt,          |	|dd	d
d	}t.          dz   t0          j                            |          z   dz   }t'          j        |
j        d          }|                                 t=                       t=          d           t=          d|            t=                       |rP	 ddl}|                     |dd           n2# tB          $ r%}t          "                    d|           Y d}~nd}~ww xY wd}	 |#                    |          r/t          j        }t          j        }|rtI          d| d          n(t                              d           tK                      }	 |
&                                 n# tB          $ r Y nw xY w	 |
'                                 n# tB          $ r Y nw xY w|(                    d           ng# 	 |
&                                 n# tB          $ r Y nw xY w	 |
'                                 n# tB          $ r Y nw xY w|(                    d           w xY w|stI          dd          tS          |||||          }tU          ||          S ) a  Run the interactive browser OAuth flow and persist credentials.

    Args:
        force_relogin: If False and valid creds already exist, return them.
        open_browser: If False, skip webbrowser.open and print the URL only.
        callback_wait_seconds: Max seconds to wait for the browser callback.
        project_id: Initial GCP project ID to bake into the stored creds.
                    Can be discovered/updated later via update_project_ids().
    z9Google OAuth credentials already present; skipping login.   z?Headless environment detected; using paste-mode OAuth fallback.http://:Nr   S256offlineconsent	rz   r+  response_typescoper\  code_challengecode_challenge_methodaccess_typeprompt?#hermesT)targetdaemonu,   Opening your browser to sign in to Google…z,If it does not open automatically, visit:
  r   r8   )new	autoraisezwebbrowser.open failed: %sr  zAuthorization failed: !google_oauth_authorization_failedr   u=   Callback server timed out — offering manual paste fallback.g       @z)No authorization code received. Aborting.google_oauth_no_coderz   r|   r   )+r   r   r   r   r   r   r   r   r   r  _paste_mode_loginr~  DEFAULT_REDIRECT_PORTr|  rd  rS  rT  rU  rV  rB  rC  rW  OAUTH_SCOPESAUTH_ENDPOINTr  r   r  Threadserve_foreverstartprint
webbrowserrB   r#  r   rD  r   _prompt_paste_fallbackshutdownserver_closejoinr3  _persist_token_response)r  r  r  r   existingrz   r|   r   r   r\  rz  portr+  rW  rn  auth_urlserver_threadr  r   r   r!  
token_resps                         r&   start_oauth_flowr  8  s<      #%% 	- 	KKSTTTO"$$I&((M-//Hi!"%%E ~~ c, cUVVV 9eYWabbb()>??LFDB]BBTB=BBL+0(*.'+/(OE"' $#!' 
 
F s"V\%;%;F%C%CCiOH$F,@NNNM	GGG	
8999	
D(
D
DEEE	GGG <	<OOH!tO<<<< 	< 	< 	<LL5s;;;;;;;;	< D(::3:44 
	,(6D)8E &4U44<    KKWXXX)++D	OO 	 	 	D		!!!! 	 	 	D	3''''	OO 	 	 	D		!!!! 	 	 	D	3'''' 
7'
 
 
 	

 h=  J #:*EEEEs   G- -
H7HH"A-K2 J% %
J21J26K 
KK2M4L	M	
LMLML/.M/
L<9M;L<<Mr   r\  c           
        dt            dt           t           }||dt          ||dddd	}t          dz   t
          j                            |          z   d	z   }t                       t          d
           t          d|            t                       t          d           t          d           t                       t                      }	|	st          dd          t          |	| |||          }
t          |
|          S )z/Run OAuth flow without a local callback server.r  r  r   r  r  r  r  r  r  z)Open this URL in a browser on any device:z  zGAfter signing in, Google will redirect to localhost (which won't load).z7Copy the full URL from your browser and paste it below.zNo authorization code provided.r  r   r  r  )r|  r  rd  r  r  r  r   r  r  r  r   r3  r  )r   r   r\  rz   r|   r   r+  rn  r  r   r  s              r&   r  r    s'    T]SS-BSMSSL$#!' 
 
F s"V\%;%;F%C%CCiOH	GGG	
5666	/x//	GGG	
STTT	
CDDD	GGG!##D _@G]^^^^h=  J #:*EEEEr'   c                 \   t                       t          d           t          d                                          } | sd S |                     d          s|                     d          rct          j                            |           }t          j                            |j                  }|	                    d          pdgd         pd S |                     d          rGt          j                            | d	d                    }|	                    d          pdgd         pd S | S )
NzSPaste the full redirect URL Google showed you, OR just the 'code=' parameter value.zCallback URL or code: r  zhttps://r   r{   r   r  r8   )
r  inputr   
startswithr  r   rc  rg  rh  r   )r   rm  rn  s      r&   r  r    s   	GGG	
_```
(
)
)
/
/
1
1C t
~~i   7CNN:$>$> 7&&s++&&v|44

6""*rdA.6$6
~~c 7&&s122w//

6""*rdA.6$6Jr'   r  r  c               j   t          |                     dd          pd                                          }t          |                     dd          pd                                          }t          |                     dd          pd          }|r|st	          dd          t          ||t          t          j                    t          d	|          z   d
z            t          |          |d          }t          |           t                              dt                                 |S )Nr   r{   r   r@  r   z<Google token response missing access_token or refresh_token.&google_oauth_incomplete_token_responser   r   r   r   z$Google OAuth credentials saved to %s)r   r   r   r   r   r   rG   rI   r:  r	  r   r   r0   )r  r   r   r   r@  r   s         r&   r  r    s+   
 z~~nb99?R@@FFHHL
;;ArBBHHJJMZ^^L!449::J 
} 
J9
 
 
 	
 !#	c"j&9&99TABB--  E U
KK68I8K8KLLLLr'   c                 d    t          d          } | j        | j        | j        | j        | j        dS )zHRun the login flow and return a dict matching the credential pool shape.T)r  )r   r   expires_at_msr   r   )r  r   r   r   r   r   )r   s    r&   run_gemini_oauth_login_purer    s>    4000E*,)&  r'   c                 l    dD ]0} t          j        |           pd                                }|r|c S 1dS )z9Return a GCP project ID from env vars, in priority order.)HERMES_GEMINI_PROJECT_IDGOOGLE_CLOUD_PROJECTGOOGLE_CLOUD_PROJECT_IDr{   )rA   r   r   )varvals     r&   resolve_project_id_from_envr    sN      
 y~~#**,, 	JJJ	2r'   )r   r   )r4   r5   )r   r^   )r   rw   r   )r   r   )r   r   r   r   ru  )r  r   r   r\   r  r5   r   r   )r   r   r   r   r+  r   rz   r,  r|   r,  r  r5   r   r   )
r   r   rz   r,  r|   r,  r  r5   r   r   )r   r   r  r5   r   r   )r<  r   r   r   r~   )r   r   r   r   r   r   )rv  r   r   rw  )r   r   )
r  r   r  r   r  r5   r   r   r   r   )r   r   r   r   r\  r   rz   r   r|   r   r   r   r   r   )r   r,  )r  r   r   r   r   r   r   )hr+   
__future__r   r   
contextlibr   http.serverry  r   loggingrA   r   r  rB  rG   urllib.errorr  urllib.parseurllib.requestdataclassesr   pathlibr   typingr   r   r   r	   hermes_constantsr   	getLoggerr(   r   r   r   _PUBLIC_CLIENT_ID_PROJECT_NUM_PUBLIC_CLIENT_ID_HASH_PUBLIC_CLIENT_SECRET_SUFFIXr   r   re_reutilsr   compiler   r   r   r   r  r1  r9  r  r  r|  rd  r   TOKEN_REQUEST_TIMEOUT_SECONDSCALLBACK_WAIT_SECONDSrE  r  RuntimeErrorr   r0   r3   localr>   contextmanagerr[   r]   r   rv   r   r   r   r   r   r   r   r   r	  r  r*  r3  r6  r:  r;  LockrA  rO  rQ  rz  BaseHTTPRequestHandlerrS  rl  rk  r~  r  r  r  r  r  r  r  r/   r'   r&   <module>r     s<  ' ' 'R # " " " " "             				                    ! ! ! ! ! !       - - - - - - - - - - - - , , , , , ,		8	$	$" *1  !/ ; =  % " "'= " " "  B#?AA                  S[W   %B   3;YZZ "s{#ABB  ?6C 7   !   $    T     |   < < < <9 9 9 9 io /C J" J" J" J" J"b (*  ) ) ) )1 1 1 1h$ $ $ $D         ,    S S S S S S S S8 'N 'N 'N 'N 'N 'N 'N 'N\   (   0	] 	] 	] 	] " " " "T  $#'25 5 5 5 5 56  $#'25 5 5 5 5 52 ;X     ( 13  2 2 2 2'))  5: D D D D D DV	 	 	 	 	 2" 2" 2" 2" 2"DK> 2" 2" 2"j 1F 
, 
, 
, 
, 
,9 9 9 9  #8iF iF iF iF iF iFX(F (F (F (FV   (      <	 	 	 	 
 
 
 
 
 
r'   