Last active
January 10, 2024 14:27
-
-
Save merrickluo/313cdcd3a15a941c04a487eedc58ce17 to your computer and use it in GitHub Desktop.
use copilot as gptel backend
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;; copilot.el --- copilot backend for gptel -*- lexical-binding: t; -*- | |
;; | |
;; Copyright (C) 2024 Merrick Luo | |
;; | |
;; Author: Merrick Luo <merrick@luois.me> | |
;; Maintainer: Merrick Luo <merrick@luois.me> | |
;; Created: January 10, 2024 | |
;; Modified: January 10, 2024 | |
;; Version: 0.0.1 | |
;; Keywords: comm | |
;; Homepage: https://github.com/merrickluo/gptel-copilot | |
;; Package-Requires: ((emacs "24.3") (gptel "0.0.1")) | |
;; | |
;; This file is not part of GNU Emacs. | |
;; | |
;;; Commentary: | |
;; use copilot chat api as backend for gptel. | |
;; | |
;; | |
;;; Code: | |
(require 'json) | |
(require 'gptel-openai) | |
(defun gptel-copilot--random-hex(len) | |
"Generate random hex string with length LEN." | |
(format (format "%%0%dx" len) (random (expt 16 len)))) | |
(defun gptel-copilot--request-id () | |
"Generate a new request ID in the same format used by Copilot Chat." | |
(apply #'format "%s-%s-%s-%s-%s" | |
(mapcar #'gptel-copilot--random-hex '(4 2 2 2 6)))) | |
(defun gptel-copilot--session-id () | |
"Generate a new session ID in the same format used by copilot chat." | |
(apply #'format "%s-%s-%s-%s-%s" | |
(mapcar #'gptel-copilot--random-hex '(4 2 2 2 12)))) | |
(defun gptel-copilot--machine-id () | |
"Generate a new machine ID in the same format used by copilot chat." | |
(gptel-copilot--random-hex 32)) | |
(defun gptel-copilot--read-access-token () | |
"Read the access token stored by github copilot cli." | |
(with-current-buffer (find-file-noselect "~/.copilot-cli-access-token") | |
(buffer-string))) | |
(defvar gptel-copilot--token-response nil) | |
(defun gptel-copilot--request-token () | |
"Get new copilot access token from github." | |
(let* ((access-token (gptel-copilot--read-access-token)) | |
(url-request-extra-headers `(("Authorization" . ,(format "token %s" access-token))))) | |
(if (string-empty-p access-token) | |
(error "Please login to the copilot cli first.")) | |
(with-current-buffer (url-retrieve-synchronously "https://api.github.com/copilot_internal/v2/token") | |
(goto-char url-http-end-of-headers) | |
(setq gptel-copilot--token-response (json-read))))) | |
(defun gptel-copilot-key () | |
"Get gptel copilot token, try cache in memory first." | |
(if-let ((token (alist-get 'token gptel-copilot--token-response)) | |
(expires-at (alist-get 'expires_at gptel-copilot--token-response))) | |
(if (< (float-time) expires-at) | |
token | |
(alist-get 'token (gptel-copilot--request-token) 'token)) | |
(alist-get 'token (gptel-copilot--request-token) 'token))) | |
(defvar gptel-copilot--static-headers | |
'(("Editor-Version" . "vscode/1.83.1") | |
("Editor-Plugin-Version" . "copilot-chart/0.8.0") | |
("Openai-Organization" . "github-copilot") | |
("Openai-Intent" . "conversation-panel") | |
("Content-Type" . "text/event-stream; charset=utf-8") | |
("User-Agent" . "GitHubCopilotChat/0.8.0") | |
("Accept" . "*/*") | |
("Accept-Encoding" . "gzip,deflate,br")) | |
"Part of the headers required for copilot chat that's constant.") | |
(defun gptel-copilot-headers () | |
"Headers required for copilot chat." | |
(let ((headers gptel-copilot--static-headers)) | |
(setf (alist-get "Authorization" headers) (format "Bearer %s" (gptel-copilot-key))) | |
(setf (alist-get "X-Request-ID" headers) (gptel-copilot--request-id)) | |
(setf (alist-get "Vscode-Sessionid" headers) (gptel-copilot--session-id)) | |
(setf (alist-get "Vscode-Machineid" headers) (gptel-copilot--machine-id)) | |
headers)) | |
(defvar gptel-copilot-backend | |
(gptel-make-openai | |
"Copilot Chat" | |
:host "api.githubcopilot.com" | |
:key 'gptel-copilot-key | |
:header 'gptel-copilot-headers | |
:endpoint "/chat/completions" | |
:stream nil | |
:models '("gpt-4"))) | |
(provide 'gptel-copilot) | |
;;; gptel-copilot.el ends here |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment