
    `gj[@                    B   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d%dZd&dZd'd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!d"d#	 	 	 	 	 d.d$Z(y)/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)
IS_WINDOWSwindows_hide_flagszIv1.b507a08c87ecfe98ghp_)gho_github_pat_ghu_)COPILOT_GITHUB_TOKENGH_TOKENGITHUB_TOKEN      c                V    | j                         } | sy| j                  t              ryy)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)tokens    J/root/.hermes/venv/lib/python3.12/site-packages/hermes_cli/copilot_auth.pyvalidate_copilot_tokenr   6   s.    
 KKME#+,
     c                    t         D ]V  } t        j                  | d      j                         }|s*t	        |      \  }}|st
        j                  d| |       R|| fc S  t               }|r"t	        |      \  }}|st        d|       |dfS y)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     $ 	 ii$**,/4JE38'3 <	  E+E2
sGuM  o%%r   c                 l   g } t        j                  d      }|r| j                  |       ddt        t	        j
                         dz  dz  dz        fD ]]  }|| v rt        j                  j                  |      s(t        j                  |t        j                        sM| j                  |       _ | S )zIReturn candidate ``gh`` binary paths, including common Homebrew installs.ghz/opt/homebrew/bin/ghz/usr/local/bin/ghz.localbin)shutilwhichappendstrr   homer   pathisfileaccessX_OK)
candidatesresolved	candidates      r   _gh_cli_candidatesr7   j   s    J||D!H(# 	DIIK("U*T12 )	
 
"77>>)$9bgg)Fi() r   c            	     b   t        j                  dd      j                         } t         j                  j	                         D ci c]  \  }}|dvr|| }}}t
        rdt               ini }t               D ]r  }|ddg}| r|d| gz  }	 t        j                  |fddd	|d
|}|j                  dk(  s=|j                  j                         sX|j                  j                         c S  yc c}}w # t        t        j                  f$ r!}t        j                  d||       Y d}~d}~ww xY w)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   >   r   r   creationflagsauthr   z
--hostnameTr   )capture_outputtexttimeoutenvz#gh CLI token lookup failed (%s): %sNr   )r   r   r   environitemsr   r   r7   
subprocessrunFileNotFoundErrorTimeoutExpiredr   debug
returncodestdout)	hostnamekv	clean_env_popen_kwargsgh_pathcmdresultexcs	            r   r!   r!      s>    yy*B/557H #%**"2"2"4 ;$!Q99 A ;I ; @J_&8&:;rM%' )(L(++C	^^#  F !fmm&9&9&;==&&((#)$ -;" ":#<#<= 	LL>M	s   C.
C44D.D))D.z
github.comi,  )hosttimeout_secondsc                   ddl }ddl}| j                  d      }d| d}d| d}|j                  j	                  t
        dd      j                         }|j                  j                  ||d	d
dd      }	 |j                  j                  |d      5 }t        j                  |j                         j                               }	ddd       	j%                  dd      }|	j%                  dd      }|	j%                  dd      }t'        |	j%                  dt(              d      }|r|st#        d       yt#                t#        d|        t#        d|        t#                t#        ddd       t+        j,                         |z   }t+        j,                         |k  rt+        j.                  |t0        z          |j                  j	                  t
        |dd       j                         }|j                  j                  ||d	d
dd      }	 |j                  j                  |d!      5 }t        j                  |j                         j                               }ddd       j%                  d#      rt#        d$       |d#   S |j%                  d%d      }|d&k(  rt#        d"dd       '|d'k(  rM|j%                  d      }t3        |t4        t6        f      r|dkD  rt5        |      }n|d(z  }t#        d"dd       y|d)k(  rt#                t#        d*       y|d+k(  rt#                t#        d,       y|rt#                t#        d-|        yt+        j,                         |k  rt#                t#        d.       y# 1 sw Y   xY w# t        $ r.}
t        j!                  d|
       t#        d|
        Y d}
~
yd}
~
ww xY w# 1 sw Y   kxY w# t        $ r t#        d"dd       Y gw xY w)/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   r>   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)rW   rc   
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   rl   printgetmax_DEVICE_CODE_POLL_INTERVALtime	monotonicsleep_DEVICE_CODE_POLL_SAFETY_MARGIN
isinstanceintfloat)rR   rS   urllibdomaindevice_code_urlaccess_token_urlr]   reqrespdevice_datarQ   ra   rb   rc   rd   deadline	poll_datapoll_reqrP   rl   server_intervals                        r   copilot_device_code_loginr      s    [[F (:;O!&)BC <<!!,#  vx 	
 ..
 
 (?+
 ! C^^##C#4 	;**TYY[%7%7%9:K	; #'9;\]R0I//-4K;??:/IJANHi:; 
G	-.>-?
@A		{
+,	G	
*$? ~~/1H
..
X
%

8==>LL**0&H,
  68	 	 >>)), C/ * 
	''"'= :DIIK$6$6$89: ::n%&M.))

7B'++#2T*k!$jj4O/C<8_q=P/A#2T*o%G@Ao%G34G089m ..
X
%p 
G	
67i	; 	; BCH<SEBCX: : 	#2T*	sZ   9N 2NN O 02O"O N	N 	O$OOOO O32O3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.7c                j    ddl }|j                  | j                               j                         dd S )zNShort fingerprint of a raw token for cache keying (avoids storing full token).r   N   )hashlibsha256rw   	hexdigest)	raw_tokenr   s     r   _token_fingerprintr   -  s-    >>)**,-779#2>>r   g      $@r`   c          	        ddl }t        |       }t        j                  |      }|r)|\  }}}t	        j                         |t
        z
  k  r|||fS |j                  j                  t        dd|  t        dt        d      }	 |j                  j                  ||      5 }	t        j                  |	j                         j                               }
ddd       
j                  d
d      }|
j                  dd      }|st#        d      |rt%        |      nt	        j                         dz   }d}|
j                  d      }t'        |t(              r?t+        |j                  d      xs d      j-                         j/                  d      }|r|}|st1        |      }|||ft        |<   t2        j5                  d||       |||fS # 1 sw Y   xY w# t         $ r}t#        d	|       |d}~ww xY w)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 rY   )Authorizationr\   r[   Editor-Version)methodr^   r`   zCopilot token exchange failed: r   r   
expires_atz+Copilot token exchange returned empty tokeni  	endpointsapirU   z3Copilot token exchanged, expires_at=%s, base_url=%s)rq   r   r   r   r   _JWT_REFRESH_MARGIN_SECONDSrx   ry   _TOKEN_EXCHANGE_URL_EXCHANGE_USER_AGENT_EDITOR_VERSIONrz   r{   r|   r}   r~   r   r"   r   r   dictr.   r   rs   _derive_base_url_from_proxy_epr   rF   )r   r>   r   fpcached	api_tokenr   base_urlr   r   r]   rQ   r   api_endpoints                 r   exchange_copilot_tokenr   3  s     	I	&B ^^BF*0'	:x99;&AAAj(22
..
 
 %i[1.(-	
 ! 	CK^^##C#9 	4T::diik0023D	4
 "%I,*JFGG '1z"diikD6HJ #H%I)T"9==/526<<>EEcJ#H1)<X6JrN
LL=
 j(**E	4 	4 K:3%@AsJKs0   G  2GG GG 	G/G**G/c                   ddl }|j                  d|       }|sy|j                  d      }dD ]#  }|j                  |      s|t	        |      d } n |j                  d      }|j                  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]+)re   )rV   zhttp://rU   zproxy.zapi.rV   )researchgroupr   lenrs   )r   r   mproxy_epprefixapi_hosts         r   r   r     s     
		159AwwqzH) v&F-H s#H 8$HS]^44hZ  r   c                    | s| dfS 	 t        |       \  }}}||fS # t        $ r$}t        j                  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   rF   )r   r   _r   rQ   s        r   get_copilot_api_tokenr     s[     $!7	!B	1h("" I3O$s    	AAAATF)is_agent_turn	is_visionc                ,    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   rZ   zvscode-chatzconversation-editsagentuser)r   r\   zCopilot-Integration-IdzOpenai-Intentzx-initiatortruezCopilot-Vision-Request )r   r   r^   s      r   copilot_request_headersr     s3     +'"/-"/wVG ,2()Nr   )r   r.   returnztuple[bool, str])r   ztuple[str, str])r   z	list[str])r   Optional[str])rR   r.   rS   r   r   r   )r   r.   r   r.   )r   r.   r>   r   r   z tuple[str, float, Optional[str]])r   r.   r   r   )r   r.   r   ztuple[str, Optional[str]])r   boolr   r   r   zdict[str, str]))__doc__
__future__r   r{   loggingr   r+   rB   r   pathlibr   typingr   hermes_cli._subprocess_compatr   r   	getLogger__name__r   rv   r   _SUPPORTED_PREFIXESr   r   r   r   r'   r7   r!   r   r   __annotations__r   r   r   r   r   r   r   r   r   r   r   r   <module>r      s  $ #   	      H			8	$ 1  5  H   "# *>*!P  x
x x 	x~ ;=
7 <!  I "1 ? @D I+X!@4   	r   