try:
import requests, time, json # If changing, remember to change below
from flask import Flask, request
except ModuleNotFoundError:
print("""You are missing a required package to run this script.
If you press enter, it will attempt to install the missing packages (flask and requests).
This is equivalent to running: pip install flask requests
If you don't know what this means, press enter.""")
input()
import subprocess, sys
try:
subprocess.run([sys.executable, "-m", "pip", "install", "flask", "requests"], check=True)
print("\n"*100) # Clear screen
except Exception as e:
print(f"""
Got {e} while trying to install.
You may be able to install them manually.
Open Powershell and install Flask/Requests by typing the following: pip install flask requests
Press enter and wait for Flask/Requests to finish installing, then close Powershell.""")
input()
exit()
import requests, time, json
from flask import Flask, request
app = Flask(__name__)
token = None
token_error = []
telemetry_state = False
model_cache = None
api_url = "https://api.githubcopilot.com"
client_id = "01ab8ac9400c4e429b23"
copilot_integration_id = "vscode-chat"
# copilot_language_server_version = "1.256.0" # version not autoupdated
# editor_plugin_version_copilot = "copilot/1.256.0" # version not autoupdated
editor_plugin_version_copilot_chat = "copilot-chat/0.23.2"
editor_version = "vscode/1.96.3"
openai_intent_completions = "conversation-panel"
openai_intent_models = "model-access"
openai_organization = "github-copilot"
# user_agent_copilot = "GithubCopilot/1.256.0" # version not autoupdated
user_agent_copilot_chat = "GitHubCopilotChat/0.23.2"
x_github_api_version = "2024-12-15" # version not autoupdated
default_params = {
"messages": [],
"model": "claude-3.5-sonnet",
"temperature": 0.1,
"top_p": 1,
"max_tokens": 4096,
"n": 1,
"stream": False
}
login_headers = {
"accept": "application/json",
"user-agent": "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)",
"accept-encoding": "gzip,deflate"
}
def setup():
resp = requests.post(f"https://github.com/login/device/code?client_id={client_id}&scope=user:email", headers=login_headers)
# Parse the response json
resp_json = resp.json()
device_code = resp_json.get("device_code")
user_code = resp_json.get("user_code")
verification_uri = resp_json.get("verification_uri")
interval = resp_json.get("interval", 5)
# Print the user code and verification uri
print(f"Please visit {verification_uri} and enter code {user_code} to authenticate.")
while True:
time.sleep(interval)
resp = requests.post(f"https://github.com/login/oauth/access_token?client_id={client_id}&device_code={device_code}&grant_type=urn:ietf:params:oauth:grant-type:device_code", headers=login_headers)
# Parse the response json, isolating the access_token
resp_json = resp.json()
access_token = resp_json.get("access_token")
if access_token:
break
if "error" in resp_json and resp_json["error"] == "expired_token":
print(resp_json.get("error_description", resp_json["error"]))
setup()
return
# Save the access token to a file
with open(".copilot_token", "w") as f:
f.write(access_token)
print("Authentication success!")
def get_token():
global token, telemetry_state, api_url
# Check if the .copilot_token file exists
while True:
try:
with open(".copilot_token", "r") as f:
access_token = f.read()
break
except FileNotFoundError:
setup()
# Get a session with the access token
resp = requests.get("https://api.github.com/copilot_internal/v2/token", headers={
"authorization": f"token {access_token}",
"editor-plugin-version": editor_plugin_version_copilot_chat,
"editor-version": editor_version,
"user-agent": user_agent_copilot_chat,
"x-github-api-version": x_github_api_version
})
# worth noting that vscode requests this twice, one for Copilot and once for CopilotChat
# this one mimics CopilotChat
# this previously used the Copilot one, here is that if you need it:
# "authorization": f"token {access_token}",
# "x-github-api-version": x_github_api_version,
# "editor-version": editor_version_vscode,
# "editor-plugin-version": editor_plugin_version_copilot,
# "copilot-language-server-version": copilot_language_server_version,
# "user-agent": user_agent_copilot
# Parse the response json, isolating the token
resp_json = resp.json()
token = resp_json.get("token")
# Check if telemetry is on
if "telemetry" in resp_json and resp_json["telemetry"] != "disabled":
telemetry_state = resp_json["telemetry"]
else:
telemetry_state = False
if "endpoints" in resp_json and "api" in resp_json["endpoints"]:
api_url = resp_json["endpoints"]["api"]
# Find errors (if any) to display on next generation
if token is None:
token_error.clear()
for i in resp_json:
token_error.append(f"{i}: {resp_json[i]}")
print("\n".join(token_error))
def update_check():
# Checks for updated version numbers for VSCode and GitHub Copilot Chat
global editor_version, editor_plugin_version_copilot_chat, user_agent_copilot_chat
print("Checking for new versions of VSCode and GitHub Copilot.")
try:
resp = requests.get("https://update.code.visualstudio.com/api/update/win32-x64-user/stable/9df03c6d6ce97c6645c5846f6dfa2a6a7d276515")
vscode_version_new = "vscode/" + resp.json()["name"]
copilot_version_new = None
resp = requests.get(f"https://www.vscode-unpkg.net/_gallery/github/copilot-chat/latest")
for version in resp.json()["versions"]:
is_prerelease = False
for prop in version["properties"]:
if prop["key"] == "Microsoft.VisualStudio.Code.PreRelease" and prop["value"] == "true":
is_prerelease = True
if not is_prerelease:
copilot_version_new = version["version"]
any_updated = False
if editor_version != vscode_version_new:
any_updated = True
print(f"editor_version: {editor_version} -> {vscode_version_new}")
editor_version = vscode_version_new
if copilot_version_new is not None:
copilot_version_new1 = "copilot-chat/" + copilot_version_new
if editor_plugin_version_copilot_chat != copilot_version_new1:
any_updated = True
print(f"editor_plugin_version_copilot_chat: {editor_plugin_version_copilot_chat} -> {copilot_version_new1}")
editor_plugin_version_copilot_chat = copilot_version_new1
copilot_version_new2 = "GitHubCopilotChat/" + copilot_version_new
if user_agent_copilot_chat != copilot_version_new2:
any_updated = True
print(f"user_agent_copilot_chat: {user_agent_copilot_chat} -> {copilot_version_new2}")
user_agent_copilot_chat = copilot_version_new2
if any_updated:
print("These changes are not persistent.\n")
else:
print("None found.\n")
except:
print("Check failed.")
def format_error(message, formatting="text"):
return {"choices": [{"message": {"content": f"```{formatting}\n{message}\n```", "role": "assistant"}}]}
def get_models():
return requests.get(f"{api_url}/models", headers={
"authorization": f"Bearer {token}",
"copilot-integration-id": copilot_integration_id,
"editor-plugin-version": editor_plugin_version_copilot_chat,
"editor-version": editor_version,
"openai-intent": openai_intent_models,
"openai-organization": openai_organization,
"user-agent": user_agent_copilot_chat,
"x-github-api-version": x_github_api_version,
"accept-encoding": "gzip, deflate, br, zstd"
}) # missing vscode-machineid, vscode-sessionid, x-request-id, and some other things that probably don't matter
@app.route("/models")
def models():
global model_cache
if model_cache is not None: # Potential for a bad response to require a restart
return model_cache
resp = get_models()
# Cache model list
if resp.status_code == 200:
model_cache = resp.text
# Refresh token if expired
else:
get_token()
resp = get_models()
return resp.text, resp.status_code
def do_completion(j):
resp = requests.post(f"{api_url}/chat/completions", headers={
"authorization": f"Bearer {token}",
"content-type": "application/json",
"copilot-integration-id": copilot_integration_id,
"editor-plugin-version": editor_plugin_version_copilot_chat,
"editor-version": editor_version,
"openai-intent": openai_intent_completions,
"openai-organization": openai_organization,
"user-agent": user_agent_copilot_chat,
"x-github-api-version": x_github_api_version,
"accept-encoding": "gzip, deflate, br, zstd"
}, json=j) # missing same stuff as /models
return resp
def completions(req):
if token is None:
return format_error("\n".join(token_error), "yaml")
# Telemetry check
if telemetry_state:
get_token()
if telemetry_state:
return format_error(f"You have telemetry {telemetry_state}. Go to https://github.com/settings/copilot and uncheck \"Allow GitHub to use my data for product improvements\".")
if "stream" in req and req["stream"]:
return "data: " + json.dumps({"choices":[{"delta":{"content":"```ini\nTurn off streaming.\n```","role":"assistant"}}]}) + "\n\n"
try:
resp = do_completion(req)
# Refresh token if expired
try:
json.loads(resp.text)
except json.JSONDecodeError:
print(f"{resp.text.strip()}")
print("Trying to get new token.")
get_token()
resp = do_completion(req)
# Handle other errors
try:
json.loads(resp.text)
except json.JSONDecodeError:
return format_error(resp.text)
# Check for redirected model
try:
req_model = req.get("model")
resp_model = json.loads(resp.text).get("model")
if req_model is not None and resp_model is not None and req_model != resp_model:
message = f"Requested \"{req_model}\" but got \"{resp_model}\"."
# Check for same model/allowed redirects (i.e. gpt-4o -> gpt-4o-2024-05-13)
# Edge case where gpt-4 -> gpt-4o appears to be an allowed redirect
if resp_model.startswith(resp_model) and ("gpt-4o" in req_model or "gpt-4o" not in resp_model):
message += " (Valid Redirect)"
print(message)
else:
print(message)
return format_error(message)
except:
print("Failed to check for model mismatch. This is probably fine.")
return resp.text
except requests.exceptions.ConnectionError:
return format_error("Connection Error")
# OpenAI compatible
@app.route("/chat/completions", methods=["POST"])
def oai():
return completions(request.json)
# Anthropic compatible
@app.route("/v1/messages", methods=["POST"])
def ant():
model = request.json.get("model", "")
if "sonnet" not in model or model == "claude-3-sonnet-20240229":
return {"type": "message","content": [{"type": "text", "text": "```text\nModel not supported. Use Claude 3.5 Sonnet.\n```"}]}
req = request.json
messages = [{"role": "system", "content": request.json.get("system")}] + request.json.get("messages")
req["messages"] = []
for role in messages:
for content in role["content"]:
req["messages"].append({"role": role.get("role"), "content": content.get("text")})
req["model"] = "claude-3.5-sonnet"
req["stop"] = req["stop_sequences"]
req.pop("stop_sequences")
req.pop("system")
resp = completions(req)
ant_resp = {"type": "message", "content": [{"type": "text", "text": json.loads(resp)["choices"][0]["message"]["content"]}]}
return ant_resp
def main():
update_check()
get_token()
app.run()
if __name__ == "__main__":
main()]