copilot2Serice / main.go
Chave's picture
Upload 10 files
b54b038 verified
raw
history blame
17.2 kB
package main
import (
"bufio"
"bytes"
"compress/gzip"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"html/template"
"io"
"log"
"net/http"
"net/url"
"os"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/joho/godotenv"
"github.com/patrickmn/go-cache"
"github.com/tidwall/gjson"
)
const tokenUrl = "https://api.github.com/copilot_internal/v2/token"
const completionsUrl = "https://api.githubcopilot.com/chat/completions"
const embeddingsUrl = "https://api.githubcopilot.com/embeddings"
var requestUrl = ""
type Model struct {
ID string `json:"id"`
Object string `json:"object"`
Created int `json:"created"`
OwnedBy string `json:"owned_by"`
Root string `json:"root"`
Parent *string `json:"parent"`
}
type ModelList struct {
Object string `json:"object"`
Data []Model `json:"data"`
}
var version = "v0.6.1"
var port = "8081"
var client_id = "Iv1.b507a08c87ecfe98"
func main() {
err := godotenv.Load()
if err == nil {
// 从环境变量中获取配置值
portEnv := os.Getenv("PORT")
if portEnv != "" {
port = portEnv
version = os.Getenv("VERSION")
}
}
log.Printf("Server is running on port %s, version: %s\n", port, version)
log.Printf("client_id: %s\n", client_id)
gin.SetMode(gin.ReleaseMode)
r := gin.Default()
// CORS 中间件
r.Use(func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
})
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, `
curl --location 'http://127.0.0.1:8081/v1/chat/completions' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ghu_xxx' \
--data '{
"model": "gpt-4",
"messages": [{"role": "user", "content": "hi"}]
}'`)
})
r.GET("/v1/models", func(c *gin.Context) {
c.JSON(http.StatusOK, models())
})
r.POST("/v1/chat/completions", func(c *gin.Context) {
c.Header("Cache-Control", "no-cache, must-revalidate")
c.Header("Connection", "keep-alive")
requestUrl = completionsUrl
forwardRequest(c)
})
r.POST("/v1/embeddings", func(c *gin.Context) {
c.Header("Cache-Control", "no-cache, must-revalidate")
c.Header("Connection", "keep-alive")
requestUrl = embeddingsUrl
forwardRequest(c)
})
// 获取ghu
t, err := loadTemplate()
if err != nil {
panic(err)
}
r.SetHTMLTemplate(t)
r.GET("/auth", func(c *gin.Context) {
// 获取设备授权码
deviceCode, userCode, err := getDeviceCode()
if err != nil {
c.String(http.StatusOK, "获取设备码失败:"+err.Error())
return
}
// 使用 deviceCode 和 userCode
fmt.Println("Device Code: ", deviceCode)
fmt.Println("User Code: ", userCode)
c.HTML(http.StatusOK, "/html/auth.tmpl", gin.H{
"title": "Get Copilot Token",
"deviceCode": deviceCode,
"userCode": userCode,
})
})
r.POST("/auth/check", func(c *gin.Context) {
returnData := map[string]string{
"code": "1",
"msg": "",
"data": "",
}
deviceCode := c.PostForm("deviceCode")
if deviceCode == "" {
returnData["msg"] = "device code null"
c.JSON(http.StatusOK, returnData)
return
}
token, err := checkUserCode(deviceCode)
if err != nil {
returnData["msg"] = err.Error()
c.JSON(http.StatusOK, returnData)
return
}
if token == "" {
returnData["msg"] = "token null"
c.JSON(http.StatusOK, returnData)
return
}
returnData["code"] = "0"
returnData["msg"] = "success"
returnData["data"] = token
c.JSON(http.StatusOK, returnData)
return
})
r.POST("/auth/checkGhu", func(c *gin.Context) {
returnData := map[string]string{
"code": "1",
"msg": "",
"data": "",
}
ghu := c.PostForm("ghu")
if ghu == "" {
returnData["msg"] = "ghu null"
c.JSON(http.StatusOK, returnData)
return
}
if !strings.HasPrefix(ghu, "gh") {
returnData["msg"] = "ghu 格式错误"
c.JSON(http.StatusOK, returnData)
return
}
info := checkGhuToken(ghu)
returnData["code"] = "0"
returnData["msg"] = "success"
returnData["data"] = info
c.JSON(http.StatusOK, returnData)
return
})
r.Run(":" + port)
}
func forwardRequest(c *gin.Context) {
var jsonBody map[string]interface{}
if err := c.ShouldBindJSON(&jsonBody); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Request body is missing or not in JSON format"})
return
}
ghuToken := strings.Split(c.GetHeader("Authorization"), " ")[1]
if !strings.HasPrefix(ghuToken, "gh") {
c.JSON(http.StatusBadRequest, gin.H{"error": "auth token not found"})
log.Printf("token 格式错误:%s\n", ghuToken)
return
}
// 检查 token 是否有效
if !checkToken(ghuToken) {
c.JSON(http.StatusBadRequest, gin.H{"error": "auth token is invalid"})
log.Printf("token 无效:%s\n", ghuToken)
return
}
accToken, err := getAccToken(ghuToken)
if accToken == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
sessionId := fmt.Sprintf("%s%d", uuid.New().String(), time.Now().UnixNano()/int64(time.Millisecond))
machineID := sha256.Sum256([]byte(uuid.New().String()))
machineIDStr := hex.EncodeToString(machineID[:])
accHeaders := getAccHeaders(accToken, uuid.New().String(), sessionId, machineIDStr)
client := &http.Client{}
jsonData, err := json.Marshal(jsonBody)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
isStream := gjson.GetBytes(jsonData, "stream").String() == "true"
req, err := http.NewRequest("POST", requestUrl, bytes.NewBuffer(jsonData))
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
}
for key, value := range accHeaders {
req.Header.Add(key, value)
}
resp, err := client.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
bodyString := string(bodyBytes)
log.Printf("对话失败:%d, %s ", resp.StatusCode, bodyString)
cache := cache.New(5*time.Minute, 10*time.Minute)
cache.Delete(ghuToken)
c.AbortWithError(resp.StatusCode, fmt.Errorf(bodyString))
return
}
c.Header("Content-Type", "application/json; charset=utf-8")
if isStream {
returnStream(c, resp)
} else {
returnJson(c, resp)
}
return
}
func returnJson(c *gin.Context, resp *http.Response) {
c.Header("Content-Type", "application/json; charset=utf-8")
body, err := io.ReadAll(resp.Body.(io.Reader))
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.Writer.Write(body)
return
}
func returnStream(c *gin.Context, resp *http.Response) {
c.Header("Content-Type", "text/event-stream; charset=utf-8")
// 创建一个新的扫描器
scanner := bufio.NewScanner(resp.Body)
// 使用Scan方法来读取流
for scanner.Scan() {
line := scanner.Bytes()
// 替换 "content":null 为 "content":""
modifiedLine := bytes.Replace(line, []byte(`"content":null`), []byte(`"content":""`), -1)
// 将修改后的数据写入响应体
if _, err := c.Writer.Write(modifiedLine); err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
// 添加一个换行符
if _, err := c.Writer.Write([]byte("\n")); err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
}
if scanner.Err() != nil {
// 处理来自扫描器的任何错误
c.AbortWithError(http.StatusInternalServerError, scanner.Err())
return
}
return
}
func models() ModelList {
jsonStr := `{
"object": "list",
"data": [
{"id": "text-search-babbage-doc-001","object": "model","created": 1651172509,"owned_by": "openai-dev"},
{"id": "gpt-4-0613","object": "model","created": 1686588896,"owned_by": "openai"},
{"id": "gpt-4", "object": "model", "created": 1687882411, "owned_by": "openai"},
{"id": "babbage", "object": "model", "created": 1649358449, "owned_by": "openai"},
{"id": "gpt-3.5-turbo-0613", "object": "model", "created": 1686587434, "owned_by": "openai"},
{"id": "text-babbage-001", "object": "model", "created": 1649364043, "owned_by": "openai"},
{"id": "gpt-3.5-turbo", "object": "model", "created": 1677610602, "owned_by": "openai"},
{"id": "gpt-3.5-turbo-1106", "object": "model", "created": 1698959748, "owned_by": "system"},
{"id": "curie-instruct-beta", "object": "model", "created": 1649364042, "owned_by": "openai"},
{"id": "gpt-3.5-turbo-0301", "object": "model", "created": 1677649963, "owned_by": "openai"},
{"id": "gpt-3.5-turbo-16k-0613", "object": "model", "created": 1685474247, "owned_by": "openai"},
{"id": "text-embedding-ada-002", "object": "model", "created": 1671217299, "owned_by": "openai-internal"},
{"id": "davinci-similarity", "object": "model", "created": 1651172509, "owned_by": "openai-dev"},
{"id": "curie-similarity", "object": "model", "created": 1651172510, "owned_by": "openai-dev"},
{"id": "babbage-search-document", "object": "model", "created": 1651172510, "owned_by": "openai-dev"},
{"id": "curie-search-document", "object": "model", "created": 1651172508, "owned_by": "openai-dev"},
{"id": "babbage-code-search-code", "object": "model", "created": 1651172509, "owned_by": "openai-dev"},
{"id": "ada-code-search-text", "object": "model", "created": 1651172510, "owned_by": "openai-dev"},
{"id": "text-search-curie-query-001", "object": "model", "created": 1651172509, "owned_by": "openai-dev"},
{"id": "text-davinci-002", "object": "model", "created": 1649880484, "owned_by": "openai"},
{"id": "ada", "object": "model", "created": 1649357491, "owned_by": "openai"},
{"id": "text-ada-001", "object": "model", "created": 1649364042, "owned_by": "openai"},
{"id": "ada-similarity", "object": "model", "created": 1651172507, "owned_by": "openai-dev"},
{"id": "code-search-ada-code-001", "object": "model", "created": 1651172507, "owned_by": "openai-dev"},
{"id": "text-similarity-ada-001", "object": "model", "created": 1651172505, "owned_by": "openai-dev"},
{"id": "text-davinci-edit-001", "object": "model", "created": 1649809179, "owned_by": "openai"},
{"id": "code-davinci-edit-001", "object": "model", "created": 1649880484, "owned_by": "openai"},
{"id": "text-search-curie-doc-001", "object": "model", "created": 1651172509, "owned_by": "openai-dev"},
{"id": "text-curie-001", "object": "model", "created": 1649364043, "owned_by": "openai"},
{"id": "curie", "object": "model", "created": 1649359874, "owned_by": "openai"},
{"id": "davinci", "object": "model", "created": 1649359874, "owned_by": "openai"},
{"id": "gpt-4-0314", "object": "model", "created": 1687882410, "owned_by": "openai"}
]
}`
var modelList ModelList
json.Unmarshal([]byte(jsonStr), &modelList)
return modelList
}
func getAccToken(ghuToken string) (string, error) {
var accToken = ""
cache := cache.New(15*time.Minute, 60*time.Minute)
cacheToken, found := cache.Get(ghuToken)
if found {
accToken = cacheToken.(string)
} else {
client := &http.Client{}
req, err := http.NewRequest("GET", tokenUrl, nil)
if err != nil {
return accToken, err
}
headers := getHeaders(ghuToken)
for key, value := range headers {
req.Header.Add(key, value)
}
resp, err := client.Do(req)
if err != nil {
return accToken, err
}
defer resp.Body.Close()
var reader interface{}
switch resp.Header.Get("Content-Encoding") {
case "gzip":
reader, err = gzip.NewReader(resp.Body)
if err != nil {
return accToken, fmt.Errorf("数据解压失败")
}
default:
reader = resp.Body
}
body, err := io.ReadAll(reader.(io.Reader))
if err != nil {
return accToken, fmt.Errorf("数据读取失败")
}
if resp.StatusCode == http.StatusOK {
accToken = gjson.GetBytes(body, "token").String()
if accToken == "" {
return accToken, fmt.Errorf("acc_token 未返回")
}
cache.Set(ghuToken, accToken, 14*time.Minute)
} else {
log.Printf("获取 acc_token 请求失败:%d, %s ", resp.StatusCode, string(body))
return accToken, fmt.Errorf("获取 acc_token 请求失败: %d", resp.StatusCode)
}
}
return accToken, nil
}
func checkToken(ghuToken string) bool {
client := &http.Client{}
url := "https://api.github.com/user"
req, err := http.NewRequest("GET", url, nil)
if err != nil {
fmt.Println(err)
return false
}
req.Header.Add("Accept", "application/vnd.github+json")
req.Header.Add("Authorization", "Bearer "+ghuToken)
req.Header.Add("X-GitHub-Api-Version", "2022-11-28")
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
return false
}
defer resp.Body.Close()
return resp.StatusCode == http.StatusOK
}
func getHeaders(ghoToken string) map[string]string {
return map[string]string{
"Host": "api.github.com",
"Authorization": "token " + ghoToken,
"Editor-Version": "vscode/1.85.1",
"Editor-Plugin-Version": "copilot-chat/0.11.1",
"User-Agent": "GitHubCopilotChat/0.11.1",
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
}
}
func getAccHeaders(accessToken, uuid string, sessionId string, machineId string) map[string]string {
return map[string]string{
"Host": "api.githubcopilot.com",
"Authorization": "Bearer " + accessToken,
"X-Request-Id": uuid,
"X-Github-Api-Version": "2023-07-07",
"Vscode-Sessionid": sessionId,
"Vscode-machineid": machineId,
"Editor-Version": "vscode/1.85.1",
"Editor-Plugin-Version": "copilot-chat/0.11.1",
"Openai-Organization": "github-copilot",
"Openai-Intent": "conversation-panel",
"Content-Type": "application/json",
"User-Agent": "GitHubCopilotChat/0.11.1",
"Copilot-Integration-Id": "vscode-chat",
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
}
}
func getDeviceCode() (string, string, error) {
requestUrl := "https://github.com/login/device/code"
body := url.Values{}
headers := map[string]string{
"Accept": "application/json",
}
body.Set("client_id", client_id)
res, err := handleRequest("POST", body, requestUrl, headers)
deviceCode := gjson.Get(res, "device_code").String()
userCode := gjson.Get(res, "user_code").String()
if deviceCode == "" {
return "", "", fmt.Errorf("device code null")
}
if userCode == "" {
return "", "", fmt.Errorf("user code null")
}
return deviceCode, userCode, err
}
func checkUserCode(deviceCode string) (string, error) {
requestUrl := "https://github.com/login/oauth/access_token"
body := url.Values{}
headers := map[string]string{
"Accept": "application/json",
}
body.Set("client_id", client_id)
body.Set("device_code", deviceCode)
body.Set("grant_type", "urn:ietf:params:oauth:grant-type:device_code")
res, err := handleRequest("POST", body, requestUrl, headers)
if err != nil {
return "", err
}
token := gjson.Get(res, "access_token").String()
return token, nil
}
func checkGhuToken(ghuToken string) string {
requestUrl := "https://api.github.com/copilot_internal/v2/token"
body := url.Values{}
headers := map[string]string{
"Authorization": "Bearer " + ghuToken,
"editor-version": "JetBrains-IU/232.10203.10",
"editor-plugin-version": "copilot-intellij/1.3.3.3572",
"User-Agent": "GithubCopilot/1.129.0",
"Host": "api.github.com",
}
res, err := handleRequest("GET", body, requestUrl, headers)
if err != nil {
return "查询失败"
}
info := gjson.Get(res, "sku").String()
if info != "" {
return info
} else {
return "未订阅"
}
}
func handleRequest(method string, body url.Values, requestUrl string, headers map[string]string) (string, error) {
client := &http.Client{}
req, err := http.NewRequest(method, requestUrl, bytes.NewBuffer([]byte(body.Encode())))
if err != nil {
return "", err
}
for key, value := range headers {
req.Header.Add(key, value)
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("status code: %d, read body error", resp.StatusCode)
}
return string(respBody), nil
}
func loadTemplate() (*template.Template, error) {
t := template.New("")
for name, file := range Assets.Files {
if file.IsDir() || !strings.HasSuffix(name, ".tmpl") {
continue
}
h, err := io.ReadAll(file)
if err != nil {
return nil, err
}
t, err = t.New(name).Parse(string(h))
if err != nil {
return nil, err
}
}
return t, nil
}