space-ace/addons/copilot/GithubCopilot.gd

190 lines
6.0 KiB
GDScript

@tool
extends "res://addons/copilot/LLM.gd"
const URL = "https://api.githubcopilot.com/chat/completions"
const AUTH_URL = "https://api.github.com/copilot_internal/v2/token"
const SYSTEM_TEMPLATE = """You are a brilliant coding assistant for the game-engine Godot. The version used is Godot 4.0, and all code must be valid GDScript!
That means the new GDScript 2.0 syntax is used. Here's a couple of important changes that were introduced:
- Use @export annotation for exports
- Use Node3D instead of Spatial, and position instead of translation
- Use randf_range and randi_range instead of rand_range
- Connect signals via node.SIGNAL_NAME.connect(Callable(TARGET_OBJECT, TARGET_FUNC))
- Same for sort_custom calls, pass a Callable(TARGET_OBJECT, TARGET_FUNC)
- Use rad_to_deg instead of rad2deg
- Use PackedByteArray instead of PoolByteArray
- Use instantiate instead of instance
- You can't use enumerate(OBJECT). Instead, use "for i in len(OBJECT):"
Remember, this is not Python. It's GDScript for use in Godot.
You may only answer in code, never add any explanations. In your prompt, there will be an !INSERT_CODE_HERE! tag. Only respond with plausible code that may be inserted at that point. Never repeat the full script, only the parts to be inserted. Treat this as if it was an autocompletion. You may continue whatever word or expression was left unfinished before the tag. Make sure indentation matches the surrounding context."""
const INSERT_TAG = "!INSERT_CODE_HERE!"
const MAX_LENGTH = 8500
const PREFERENCES_STORAGE_NAME = "user://github_copilot_llm.cfg"
const PREFERENCES_PASS = "Jr55ICpdp3M3CuWHX0WHLqg3yh4XBjbXX"
var machine_id
var session_id
var auth_token
signal auth_token_retrieved
class Message:
var role: String
var content: String
func get_json():
return {
"role": role,
"content": content
}
const ROLES = {
"SYSTEM": "system",
"USER": "user",
"ASSISTANT": "assistant"
}
func _get_models():
return [
"gpt-4-github-copilot"
]
func _set_model(model_name):
model = model_name.replace("github-copilot", "")
func _send_user_prompt(user_prompt, user_suffix):
var messages = format_prompt(user_prompt, user_suffix)
get_completion(messages, user_prompt, user_suffix)
func format_prompt(prompt, suffix):
var messages = []
var system_prompt = SYSTEM_TEMPLATE
var combined_prompt = prompt + suffix
var diff = combined_prompt.length() - MAX_LENGTH
if diff > 0:
if suffix.length() > diff:
suffix = suffix.substr(0,diff)
else:
prompt = prompt.substr(diff - suffix.length())
suffix = ""
var user_prompt = prompt + INSERT_TAG + suffix
var msg = Message.new()
msg.role = ROLES.SYSTEM
msg.content = system_prompt
messages.append(msg.get_json())
msg = Message.new()
msg.role = ROLES.USER
msg.content = user_prompt
messages.append(msg.get_json())
return messages
func gen_hex_str(length: int) -> String:
var rng = RandomNumberGenerator.new()
var result = PackedByteArray()
for i in range(length / 2):
result.push_back(rng.randi_range(0, 255))
var hex_str = ""
for byte in result:
hex_str += "%02x" % byte
return hex_str
func create_headers(token: String, stream: bool):
var contentType: String = "application/json; charset=utf-8"
if stream:
contentType = "text/event-stream; charset=utf-8"
load_config()
var uuidString: String = UUID.v4()
return [
"Authorization: %s" % ("Bearer " + token),
"X-Request-Id: %s" % uuidString,
"Vscode-Sessionid: %s" % session_id,
"Vscode-Machineid: %s" % machine_id,
"Editor-Version: vscode/1.83.1",
"Editor-Plugin-Version: copilot-chat/0.8.0",
"Openai-Organization: github-copilot",
"Openai-Intent: conversation-panel",
"Content-Type: %s" % contentType,
"User-Agent: GitHubCopilotChat/0.8.0",
"Accept: */*",
"Accept-Encoding: gzip,deflate,br",
"Connection: close"
]
func get_auth():
var headers = [
"Accept-Encoding: gzip",
"Authorization: token %s" % api_key
]
var http_request = HTTPRequest.new()
add_child(http_request)
http_request.connect("request_completed",on_auth_request_completed)
var error = http_request.request(AUTH_URL, headers, HTTPClient.METHOD_GET)
if error != OK:
emit_signal("completion_error", null)
func get_completion(messages, prompt, suffix):
if not auth_token:
get_auth()
await auth_token_retrieved
var body = {
"model": model,
"messages": messages,
"temperature": 0.7,
"top_p": 1,
"n": 1,
"stream": false,
}
var headers = create_headers(auth_token, false)
var http_request = HTTPRequest.new()
add_child(http_request)
http_request.connect("request_completed",on_request_completed.bind(prompt, suffix, http_request))
var json_body = JSON.stringify(body)
var error = http_request.request(URL, headers, HTTPClient.METHOD_POST, json_body)
if error != OK:
emit_signal("completion_error", null)
func on_auth_request_completed(result, response_code, headers, body):
var test_json_conv = JSON.new()
test_json_conv.parse(body.get_string_from_utf8())
var json = test_json_conv.get_data()
auth_token = json.token
auth_token_retrieved.emit()
func on_request_completed(result, response_code, headers, body, pre, post, http_request):
var test_json_conv = JSON.new()
test_json_conv.parse(body.get_string_from_utf8())
var json = test_json_conv.get_data()
var response = json
if !response.has("choices") :
emit_signal("completion_error", response)
return
var completion = response.choices[0].message
if is_instance_valid(http_request):
http_request.queue_free()
emit_signal("completion_received", completion.content, pre, post)
func store_config():
var config = ConfigFile.new()
config.set_value("auth", "machine_id", machine_id)
config.save_encrypted_pass(PREFERENCES_STORAGE_NAME, PREFERENCES_PASS)
func load_config():
var config = ConfigFile.new()
var err = config.load_encrypted_pass(PREFERENCES_STORAGE_NAME, PREFERENCES_PASS)
if not session_id:
session_id = gen_hex_str(8) + "-" + gen_hex_str(4) + "-" + gen_hex_str(4) + "-" + gen_hex_str(4) + "-" + gen_hex_str(25)
if err != OK:
machine_id = UUID.v4()
store_config()
return
machine_id = config.get_value("auth", "machine_id", UUID.v4())