
    i4                       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	m
Z
 ddlmZ  ej        e          ZdZdZdZd	Zd
ZdZd4dZd5dZd6dZd7dZdddd8dZi Zded<   d Zd!Zd"Zd#Z d9d%Z!d&d'd:d*Z"d9d+Z#d,d-d.d;d3Z$dS )<u  GitHub Copilot authentication utilities.

Implements the OAuth device code flow used by the Copilot CLI and handles
token validation/exchange for the Copilot API.

Token type support (per GitHub docs):
  gho_          OAuth token           ✓  (default via copilot login)
  github_pat_   Fine-grained PAT      ✓  (needs Copilot Requests permission)
  ghu_          GitHub App token      ✓  (via environment variable)
  ghp_          Classic PAT           ✗  NOT SUPPORTED

Credential search order (matching Copilot CLI behaviour):
  1. COPILOT_GITHUB_TOKEN env var
  2. GH_TOKEN env var
  3. GITHUB_TOKEN env var
  4. gh auth token  CLI fallback
    )annotationsN)Path)OptionalOv23li8tweQw6odWQebzghp_)gho_github_pat_ghu_)COPILOT_GITHUB_TOKENGH_TOKENGITHUB_TOKEN      tokenstrreturntuple[bool, str]c                n    |                                  } | sdS |                     t                    rdS dS )zYValidate that a token is usable with the Copilot API.

    Returns (valid, message).
    )FzEmpty token)Fu3  Classic Personal Access Tokens (ghp_*) are not supported by the Copilot API. Use one of:
  → `copilot login` or `hermes model` to authenticate via OAuth
  → A fine-grained PAT (github_pat_*) with Copilot Requests permission
  → `gh auth login` with the default device code flow (produces gho_* tokens))TOK)strip
startswith_CLASSIC_PAT_PREFIX)r   s    </home/ubuntu/.hermes/hermes-agent/hermes_cli/copilot_auth.pyvalidate_copilot_tokenr   .   sG    
 KKMME $##+,, 

 
 :    tuple[str, str]c                 N   t           D ]b} t          j        | d                                          }|r7t	          |          \  }}|st
                              d| |           \|| fc S ct                      }|r*t	          |          \  }}|st          d|           |dfS dS )zResolve a GitHub token suitable for Copilot API use.

    Returns (token, source) where source describes where the token came from.
    Raises ValueError if only a classic PAT is available.
     z"Token from %s is not supported: %sz5Token from `gh auth token` is a classic PAT (ghp_*). zgh auth token)r   r   )	COPILOT_ENV_VARSosgetenvr   r   loggerwarning_try_gh_cli_token
ValueError)env_varvalvalidmsgr   s        r   resolve_copilot_tokenr*   C   s     $ 	  	 i$$**,, 	 /44JE3 8'3   <	  E &+E22
s 	MMM   o%%6r   	list[str]c                 n   g } t          j        d          }|r|                     |           ddt          t	          j                    dz  dz  dz            fD ]Z}|| v rt          j                            |          r4t          j	        |t          j
                  r|                     |           [| S )zIReturn candidate ``gh`` binary paths, including common Homebrew installs.ghz/opt/homebrew/bin/ghz/usr/local/bin/ghz.localbin)shutilwhichappendr   r   homer    pathisfileaccessX_OK)
candidatesresolved	candidates      r   _gh_cli_candidatesr:   b   s    J|D!!H $(### 	DIKK("U*T122 ) )	
 
""7>>)$$ 	)9bg)F)F 	)i(((r   Optional[str]c                    t          j        dd                                          } d t           j                                        D             }t                      D ]}|ddg}| r|d| gz  }	 t          j        |ddd|	          }n?# t          t          j	        f$ r&}t                              d
||           Y d}~bd}~ww xY w|j        dk    r4|j                                        r|j                                        c S dS )aa  Return a token from ``gh auth token`` when the GitHub CLI is available.

    When COPILOT_GH_HOST is set, passes ``--hostname`` so gh returns the
    correct host's token.  Also strips GITHUB_TOKEN / GH_TOKEN from the
    subprocess environment so ``gh`` reads from its own credential store
    (hosts.yml) instead of just echoing the env var back.
    COPILOT_GH_HOSTr   c                "    i | ]\  }}|d v	||S ))r   r    ).0kvs      r   
<dictcomp>z%_try_gh_cli_token.<locals>.<dictcomp>   s3     ; ; ;$!Q999 A999r   authr   z
--hostnameTr   )capture_outputtexttimeoutenvz#gh CLI token lookup failed (%s): %sNr   )r    r!   r   environitemsr:   
subprocessrunFileNotFoundErrorTimeoutExpiredr"   debug
returncodestdout)hostname	clean_envgh_pathcmdresultexcs         r   r$   r$   w   sA    y*B//5577H; ;"*"2"2"4"4 ; ; ;I &'' ) )( 	,L(++C
	^#  FF ":#<= 	 	 	LL>MMMHHHH	 !!fm&9&9&;&;!=&&(((((4s   /B		CC  Cz
github.comi,  )hosttimeout_secondsrX   rY   floatc                8	   ddl }ddl}|                     d          }d| d}d| d}|j                            t
          dd                                          }|j                            ||d	d
dd          }	 |j        	                    |d          5 }t          j        |                                                                          }	ddd           n# 1 swxY w Y   nE# t          $ r8}
t                              d|
           t#          d|
            Y d}
~
dS d}
~
ww xY w|	                    dd          }|	                    dd          }|	                    dd          }t'          |	                    dt(                    d          }|r|st#          d           dS t#                       t#          d|            t#          d|            t#                       t#          ddd           t+          j                    |z   }t+          j                    |k     rKt+          j        |t.          z              |j                            t
          |dd                                           }|j                            ||d	d
dd          }	 |j        	                    |d!          5 }t          j        |                                                                          }ddd           n# 1 swxY w Y   n## t          $ r t#          d"dd           Y w xY w|                    d#          rt#          d$           |d#         S |                    d%d          }|d&k    rt#          d"dd           w|d'k    r`|                    d          }t1          |t2          t4          f          r|dk    rt3          |          }n|d(z  }t#          d"dd           |d)k    rt#                       t#          d*           dS |d+k    rt#                       t#          d,           dS |r"t#                       t#          d-|            dS t+          j                    |k     Kt#                       t#          d.           dS )/a  Run the GitHub OAuth device code flow for Copilot.

    Prints instructions for the user, polls for completion, and returns
    the OAuth access token on success, or None on failure/cancellation.

    This replicates the flow used by opencode and the Copilot CLI.
    r   N/zhttps://z/login/device/codez/login/oauth/access_tokenz	read:user)	client_idscopeapplication/jsonz!application/x-www-form-urlencodedHermesAgent/1.0)AcceptzContent-Type
User-Agent)dataheaders   rG   z+Failed to initiate device authorization: %su,     ✗ Failed to start device authorization: verification_urizhttps://github.com/login/device	user_coder   device_codeinterval   u*     ✗ GitHub did not return a device code.z!  Open this URL in your browser: z  Enter this code: z  Waiting for authorization...T)endflushz,urn:ietf:params:oauth:grant-type:device_code)r]   ri   
grant_type
   .access_tokenu    ✓errorauthorization_pending	slow_downr   expired_tokenu,     ✗ Device code expired. Please try again.access_deniedu     ✗ Authorization was denied.u     ✗ Authorization failed: u*     ✗ Timed out waiting for authorization.)urllib.requesturllib.parserstripparse	urlencodeCOPILOT_OAUTH_CLIENT_IDencoderequestRequesturlopenjsonloadsreaddecode	Exceptionr"   rr   printgetmax_DEVICE_CODE_POLL_INTERVALtimesleep_DEVICE_CODE_POLL_SAFETY_MARGIN
isinstanceintrZ   )rX   rY   urllibdomaindevice_code_urlaccess_token_urlrc   reqrespdevice_datarW   rg   rh   ri   rj   deadline	poll_datapoll_reqrV   rr   server_intervals                        r   copilot_device_code_loginr      su    [[F;;;;OC&CCC <!!,# #   vxx 	
 .
 
 (?+
 
 !  C^##C#44 	;*TYY[[%7%7%9%9::K	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	;   BCHHHBSBBCCCttttt
 #'9;\]]R00I//-44K;??:/IJJANNH i :;;;t 
GGG	
@.>
@
@AAA	
+	
+
+,,,	GGG	
*$???? y{{_,H
)++
 
 
8==>>>L**0&H,
 ,
   688	 	 >)), C/  * 
 
	''"'== :DIIKK$6$6$8$899: : : : : : : : : : : : : : : 	 	 	#2T****H	 ::n%% 	*&MMM.))

7B''+++#2T****k!!$jj44O/C<88 _q=P=P//A#2T****o%%GGG@AAA4o%%GGG34444 	GGG8889994m )++
 
 p 
GGG	
67774sr    C- 9C!C- !C%%C- (C%)C- -
D/7-D**D/%L 9L:L L

L L
L L21L2zdict[str, tuple[str, float]]
_jwt_cachex   z0https://api.github.com/copilot_internal/v2/tokenvscode/1.104.1zGitHubCopilotChat/0.26.7	raw_tokenc                    ddl }|                    |                                                                           dd         S )zNShort fingerprint of a raw token for cache keying (avoids storing full token).r   N   )hashlibsha256r}   	hexdigest)r   r   s     r   _token_fingerprintr   #  s>    NNN>>)**,,--7799#2#>>r   g      $@rf   rG   tuple[str, float]c          	     \   ddl }t          |           }t                              |          }|r(|\  }}t	          j                    |t
          z
  k     r||fS |j                            t          dd|  t          dt          d          }	 |j                            ||          5 }t          j        |                                                                          }	ddd           n# 1 swxY w Y   n%# t           $ r}
t#          d	|
           |
d}
~
ww xY w|	                    d
d          }|	                    dd          }|st#          d          |rt%          |          nt	          j                    dz   }||ft          |<   t&                              d|           ||fS )a  Exchange a raw GitHub token for a short-lived Copilot API token.

    Calls ``GET https://api.github.com/copilot_internal/v2/token`` with
    the raw GitHub token and returns ``(api_token, expires_at)``.

    The returned token is a semicolon-separated string (not a standard JWT)
    used as ``Authorization: Bearer <token>`` for Copilot API requests.

    Results are cached in-process and reused until close to expiry.
    Raises ``ValueError`` on failure.
    r   NGETztoken r_   )Authorizationrb   ra   Editor-Version)methodrd   rf   zCopilot token exchange failed: r   r   
expires_atz+Copilot token exchange returned empty tokeni  z&Copilot token exchanged, expires_at=%s)rw   r   r   r   r   _JWT_REFRESH_MARGIN_SECONDSr~   r   _TOKEN_EXCHANGE_URL_EXCHANGE_USER_AGENT_EDITOR_VERSIONr   r   r   r   r   r   r%   rZ   r"   rO   )r   rG   r   fpcached	api_tokenr   r   r   rc   rW   s              r   exchange_copilot_tokenr   )  s    	I	&	&B ^^BF ) &	:9;;&AAAAj((
.
 
 1i11.(-	
 
 ! 	 	CK^##C#99 	4T:diikk002233D	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 K K K@3@@AAsJK "%%I,**J HFGGG '1Hz"""dikkD6HJ,JrN
LL0   j  s<   C: )9C."C: .C22C: 5C26C: :
DDDc                    | s| S 	 t          |           \  }}|S # t          $ r'}t                              d|           | cY d}~S d}~ww xY w)a  Exchange a raw GitHub token for a Copilot API token, with fallback.

    Convenience wrapper: returns the exchanged token on success, or the
    raw token unchanged if the exchange fails (e.g. network error, unsupported
    account type). This preserves existing behaviour for accounts that don't
    need exchange while enabling access to internal-only models for those that do.
    z2Copilot token exchange failed, using raw token: %sN)r   r   r"   rO   )r   r   _rW   s       r   get_copilot_api_tokenr   a  sw      -i88	1   I3OOOs    
AA AATF)is_agent_turn	is_visionr   boolr   dict[str, str]c                ,    dddd| rdndd}|rd|d	<   |S )
z~Build the standard headers for Copilot API requests.

    Replicates the header set used by opencode and the Copilot CLI.
    r   r`   zvscode-chatzconversation-editsagentuser)r   rb   zCopilot-Integration-IdzOpenai-Intentzx-initiatortruezCopilot-Vision-Requestr?   )r   r   rd   s      r   copilot_request_headersr   u  sB     +'"/-"/;wwV G  3,2()Nr   )r   r   r   r   )r   r   )r   r+   )r   r;   )rX   r   rY   rZ   r   r;   )r   r   r   r   )r   r   rG   rZ   r   r   )r   r   r   r   r   r   )%__doc__
__future__r   r   loggingr    r/   rK   r   pathlibr   typingr   	getLogger__name__r"   r|   r   _SUPPORTED_PREFIXESr   r   r   r   r*   r:   r$   r   r   __annotations__r   r   r   r   r   r   r   r   r?   r   r   <module>r      s    $ # " " " " "   				                  		8	$	$ 1  5  H   "#    *   >   *   L  x x x x x x~ ,.
 - - - -!  I "1 ? ? ? ? @D 5! 5! 5! 5! 5! 5!p   ,        r   