using System.Linq; using System.Text; using UnityEditor; using LLM.Editor.Core; using System.Diagnostics; using LLM.Editor.Helper; using UnityEngine.Networking; using System.Threading.Tasks; using System.Collections.Generic; using Debug = UnityEngine.Debug; namespace LLM.Editor.Client { public static class GeminiApiClient { [System.Serializable] private class CommandResponse { public List commands; } private static Settings.MCPSettings _settings; private static string _systemPrompt; private static bool LoadSettings() { if (_settings) return true; var guids = AssetDatabase.FindAssets("t:MCPSettings"); if (guids.Length == 0) { Debug.LogError("[GeminiApiClient] Could not find MCPSettings asset. Please create one via Assets > Create > LLM > MCP Settings."); return false; } var path = AssetDatabase.GUIDToAssetPath(guids[0]); _settings = AssetDatabase.LoadAssetAtPath(path); return _settings; } private static bool LoadSystemPrompt() { if (!string.IsNullOrEmpty(_systemPrompt)) return true; var guids = AssetDatabase.FindAssets("MCP_SystemPrompt"); if (guids.Length == 0) { Debug.LogError("[GeminiApiClient] Could not find 'MCP_SystemPrompt.txt'. Please create this text file in your project and enter the system prompt into it."); return false; } var path = AssetDatabase.GUIDToAssetPath(guids[0]); _systemPrompt = System.IO.File.ReadAllText(path); return !string.IsNullOrEmpty(_systemPrompt); } private static string GetAuthToken() { if (!LoadSettings()) return null; try { var startInfo = new ProcessStartInfo { FileName = _settings.gcloudPath, Arguments = "auth print-access-token", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; using var process = Process.Start(startInfo); if (process == null) return null; var accessToken = process.StandardOutput.ReadToEnd().Trim(); var error = process.StandardError.ReadToEnd(); process.WaitForExit(); if (process.ExitCode == 0) return accessToken; Debug.LogError($"[GeminiApiClient] gcloud auth error: {error}"); return null; } catch (System.Exception e) { Debug.LogError($"[GeminiApiClient] Exception while getting auth token: {e.Message}"); return null; } } public static async Task SendPrompt(string prompt) { if (!LoadSettings() || !LoadSystemPrompt()) return; Debug.Log($"[GeminiApiClient] Getting auth token..."); var authToken = GetAuthToken(); if (string.IsNullOrEmpty(authToken)) { Debug.LogError("[GeminiApiClient] Failed to get authentication token. Is gcloud CLI installed and are you logged in?"); return; } Debug.Log($"[GeminiApiClient] Sending prompt to live API: {prompt}"); var chatHistory = SessionManager.LoadChatHistory(); chatHistory.Add(new Data.ChatEntry { role = "user", content = prompt }); SessionManager.SaveChatHistory(chatHistory); var url = $"https://{_settings.gcpRegion}-aiplatform.googleapis.com/v1/projects/{_settings.gcpProjectId}/locations/{_settings.gcpRegion}/publishers/google/models/{_settings.modelName}:generateContent"; // Construct the request payload, adding the system prompt first. var apiRequest = new Api.ApiRequest { system_instruction = new Api.SystemInstruction { parts = new List { new() { text = _systemPrompt } } }, contents = chatHistory.Select(entry => new Api.Content { // The API expects "model" for the assistant's role. role = entry.role == "assistant" ? "model" : entry.role, parts = new List { new() { text = entry.content } } }).ToList() }; var jsonPayload = apiRequest.ToJson(); using var request = new UnityWebRequest(url, "POST"); var bodyRaw = Encoding.UTF8.GetBytes(jsonPayload); request.uploadHandler = new UploadHandlerRaw(bodyRaw); request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); request.SetRequestHeader("Authorization", $"Bearer {authToken}"); var operation = request.SendWebRequest(); while (!operation.isDone) { await Task.Yield(); } if (request.result == UnityWebRequest.Result.Success) { var responseJson = request.downloadHandler.text; Debug.Log($"[GeminiApiClient] Response received: \n{responseJson}."); var apiResponse = responseJson?.FromJson(); if (apiResponse is { candidates: not null } && apiResponse.candidates.Any()) { var commandJson = apiResponse.candidates[0].content.parts[0].text; Debug.Log($"[GeminiApiClient] Command received: {commandJson}."); chatHistory.Add(new Data.ChatEntry { role = "model", content = commandJson }); SessionManager.SaveChatHistory(chatHistory); var commandResponse = commandJson?.FromJson(); if (commandResponse is { commands: not null }) { Debug.Log($"[GeminiApiClient] Received {commandResponse.commands.Count} commands from LLM."); CommandExecutor.SetQueue(commandResponse.commands); } else { Debug.LogError($"[GeminiApiClient] Failed to parse command structure from LLM response text: {commandJson}"); } } } else { Debug.LogError($"[GeminiApiClient] API Error: {request.error}\n{request.downloadHandler.text}"); } } } }