
    (Gj[@                    ,   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 ddlmZmZ  ej        e          ZdZdZd	Zd
ZdZdZd7dZd8dZd9dZd:dZdddd;dZi Zded <   d!Z d"Z!d#Z"d$Z#d<d&Z$d'd(d=d+Z%d>d,Z&d?d.Z'd/d0d1d@d6Z(dS )Au  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)
IS_WINDOWSwindows_hide_flagszIv1.b507a08c87ecfe98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/rurouni/.hermes/hermes-agent/hermes_cli/copilot_auth.pyvalidate_copilot_tokenr   6   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+   K   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;   j   s    J|D!!H $(### 	DIKK("U*T122 ) )	
 
""7>>)$$ 	)9bg)F)F 	)i(((r   Optional[str]c            	     B   t          j        dd                                          } d t           j                                        D             }t
          rdt                      ini }t                      D ]}|ddg}| r|d| gz  }	 t          j	        |fddd	|d
|}n?# t          t          j        f$ r&}t                              d||           Y d}~`d}~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   creationflagsauthr   z
--hostnameTr   )capture_outputtexttimeoutenvz#gh CLI token lookup failed (%s): %sNr   )r!   r"   r   environitemsr   r   r;   
subprocessrunFileNotFoundErrorTimeoutExpiredr#   debug
returncodestdout)hostname	clean_env_popen_kwargsgh_pathcmdresultexcs          r   r%   r%      sm    y*B//5577H; ;"*"2"2"4"4 ; ; ;I @JQ_&8&:&:;;rM%'' ) )( 	,L(++C	^#    FF ":#<= 	 	 	LL>MMMHHHH	 !!fm&9&9&;&;!=&&(((((4s   B  C6CCz
github.comi,  )hosttimeout_secondsr[   r\   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        |t0          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          }t3          |t4          t6          f          r|dk    rt5          |          }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/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   rI   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)ra   rm   
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#   rv   printgetmax_DEVICE_CODE_POLL_INTERVALtime	monotonicsleep_DEVICE_CODE_POLL_SAFETY_MARGIN
isinstanceintr]   )r[   r\   urllibdomaindevice_code_urlaccess_token_urlrg   reqrespdevice_datarZ   rk   rl   rm   rn   deadline	poll_datapoll_reqrY   rv   server_intervals                        r   copilot_device_code_loginr      s{    [[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	
*$???? ~/1H
.

X
%
%
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 .

X
%
%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, Optional[str]]]
_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      $@rj   rI    tuple[str, float, Optional[str]]c          	     z   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   }d}|
                    d          }t'          |t(                    rMt+          |                    d          pd                                                              d          }|r|}|st1          |          }|||ft          |<   t2                              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, base_url)``.

    The returned token is a semicolon-separated string (not a standard JWT)
    used as ``Authorization: Bearer <token>`` for Copilot API requests.
    ``base_url`` is the account-specific API host: the authoritative
    ``endpoints.api`` advertised by the exchange (enterprise/proxied
    accounts), falling back to a host derived from the token's ``proxy-ep``
    field. Individual accounts have neither, so ``base_url`` is None.

    Results are cached in-process and reused until close to expiry.
    Raises ``ValueError`` on failure.
    r   NGETztoken rc   )Authorizationrf   re   Editor-Version)methodrh   rj   zCopilot token exchange failed: r   r   
expires_atz+Copilot token exchange returned empty tokeni  	endpointsapir_   z3Copilot token exchanged, expires_at=%s, base_url=%s)r{   r   r   r   r   _JWT_REFRESH_MARGIN_SECONDSr   r   _TOKEN_EXCHANGE_URL_EXCHANGE_USER_AGENT_EDITOR_VERSIONr   r   r   r   r   r   r&   r]   r   dictr   r   r}   _derive_base_url_from_proxy_epr#   rQ   )r   rI   r   fpcached	api_tokenr   base_urlr   r   rg   rZ   r   api_endpoints                 r   exchange_copilot_tokenr   3  s     	I	&	&B ^^BF 3*0'	:x9;;&AAAAj(22
.
 
 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 #H%%I)T"" $9==//5266<<>>EEcJJ 	$#H =1)<<X6JrN
LL=  
 j(**s<   C< +9C0$C< 0C44C< 7C48C< <
DDDc                f   ddl }|                    d|           }|sdS |                    d          }dD ]0}|                    |          r|t	          |          d         } n1|                    d          }|                    d          rd|t	          d          d         z   }n|}d	| S )
a  Derive the Copilot API base URL from a proxy-ep field in the token.

    The exchanged Copilot token is a semicolon-separated string like
    ``tid=xxx;exp=xxx;proxy-ep=proxy.enterprise.githubcopilot.com;...``.
    This extracts ``proxy-ep`` and converts it to an API base URL by
    replacing the leading ``proxy.`` with ``api.``.

    Returns ``https://{api_hostname}`` or None if proxy-ep is absent.
    r   Nz(?:^|;)\s*proxy-ep=([^;\s]+)ro   )r`   zhttp://r_   zproxy.zapi.r`   )researchgroupr   lenr}   )r   r   mproxy_epprefixapi_hosts         r   r   r     s     III
		1599A twwqzzH)  v&& 	F-HE	 s##H 8$$ HS]]^^44 h   r   tuple[str, Optional[str]]c                    | s| dfS 	 t          |           \  }}}||fS # t          $ r)}t                              d|           | dfcY d}~S d}~ww xY w)a2  Exchange a raw GitHub token for a Copilot API token, with fallback.

    Convenience wrapper: returns ``(api_token, base_url)`` on success, or
    ``(raw_token, None)`` 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.

    ``base_url`` is the account-specific API endpoint advertised by the
    exchange (``endpoints.api``, with a ``proxy-ep`` fallback), or None for
    individual accounts.
    Nz2Copilot token exchange failed, using raw token: %s)r   r   r#   rQ   )r   r   _r   rZ   s        r   get_copilot_api_tokenr     s      $!7	!B!B	1h(""   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   rd   zvscode-chatzconversation-editsagentuser)r   rf   zCopilot-Integration-IdzOpenai-Intentzx-initiatortruezCopilot-Vision-Requestr@   )r   r   rh   s      r   copilot_request_headersr     sB     +'"/-"/;wwV G  3,2()Nr   )r   r   r   r   )r   r   )r   r,   )r   r<   )r[   r   r\   r]   r   r<   )r   r   r   r   )r   r   rI   r]   r   r   )r   r   r   r<   )r   r   r   r   )r   r   r   r   r   r   ))__doc__
__future__r   r   loggingr!   r0   rM   r   pathlibr   typingr   hermes_cli._subprocess_compatr   r   	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   r   <module>r      s    $ # " " " " "   				                   H H H H H H H H		8	$	$ 1  5  H   "#    *   >   *! ! ! !P  x x x x x x~ ;=
 < < < <!  I "1 ? ? ? ? @D I+ I+ I+ I+ I+ I+X! ! ! !@   4        r   