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()]
Edit Report
Pub: 26 Feb 2025 17:13 UTC
Views: 576