Skip to content

Instantly share code, notes, and snippets.

@merrickluo
Last active January 10, 2024 14:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save merrickluo/313cdcd3a15a941c04a487eedc58ce17 to your computer and use it in GitHub Desktop.
Save merrickluo/313cdcd3a15a941c04a487eedc58ce17 to your computer and use it in GitHub Desktop.
use copilot as gptel backend
;;; 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