190 lines
6.0 KiB
GDScript
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())
|