using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using LLM.Editor.Analysis;
using LLM.Editor.Api;
using LLM.Editor.Core;
using LLM.Editor.Data;
using LLM.Editor.Helper;
using UnityEditor;
using UnityEngine.Networking;
using Debug = UnityEngine.Debug;
using Object = UnityEngine.Object;
namespace LLM.Editor.Client
{
///
/// The client responsible for communicating with the live Google Gemini API.
///
public class GeminiApiClient : ILlmApiClient
{
[Serializable]
private class CommandResponse { public List commands; }
private Settings.MCPSettings _settings;
private string _systemPrompt;
private bool _isInitialized;
public async Task SendPrompt(string prompt, List stagedContext)
{
if (!await Initialize()) return;
var authToken = GetAuthToken();
if (string.IsNullOrEmpty(authToken))
{
Debug.LogError("[GeminiApiClient] Failed to get authentication token.");
return;
}
var fullPrompt = BuildInitialPrompt(prompt, stagedContext);
Debug.Log("[GeminiApiClient] Sending prompt: \n" + fullPrompt);
var chatHistory = SessionManager.LoadChatHistory();
chatHistory.Add(new ChatEntry { role = "user", content = fullPrompt });
await SendApiRequest(chatHistory, authToken);
}
public async Task SendFollowUp(string detailedContext)
{
if (!await Initialize()) return;
var authToken = GetAuthToken();
if (string.IsNullOrEmpty(authToken))
{
Debug.LogError("[GeminiApiClient] Failed to get authentication token.");
return;
}
var chatHistory = SessionManager.LoadChatHistory();
chatHistory.Add(new ChatEntry { role = "user", content = detailedContext });
await SendApiRequest(chatHistory, authToken);
}
private Task Initialize()
{
if (_isInitialized) return Task.FromResult(true);
LoadSettings();
LoadSystemPrompt();
if (!_settings || string.IsNullOrEmpty(_systemPrompt))
{
Debug.LogError("[GeminiApiClient] Initialization failed. Check if MCPSettings and MCP_SystemPrompt.txt exist.");
return Task.FromResult(false);
}
_isInitialized = true;
return Task.FromResult(true);
}
private static string BuildInitialPrompt(string prompt, List stagedContext)
{
var promptBuilder = new StringBuilder();
promptBuilder.AppendLine("User Request:");
promptBuilder.AppendLine(prompt);
if (stagedContext == null || stagedContext.All(o => o == null)) return promptBuilder.ToString();
var tier1Summary = ContextBuilder.BuildTier1Summary(stagedContext);
promptBuilder.AppendLine("\n--- Staged Context ---");
promptBuilder.AppendLine(tier1Summary);
promptBuilder.AppendLine("--- End Context ---");
return promptBuilder.ToString();
}
private async Task SendApiRequest(List chatHistory, string authToken)
{
var region = _settings.gcpRegion == "global" ? string.Empty : $"{_settings.gcpRegion}-";
var url = $"https://{region}aiplatform.googleapis.com/v1/projects/{_settings.gcpProjectId}/locations/{_settings.gcpRegion}/publishers/google/models/{_settings.modelName}:generateContent";
var systemPromptWithContext = GetWorkingContext();
var apiRequest = new ApiRequest
{
system_instruction = new SystemInstruction
{
parts = new List { new() { text = systemPromptWithContext } }
},
contents = chatHistory.Select(entry => new Content
{
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] API Response: \n{responseJson}");
var apiResponse = responseJson.FromJson();
if (apiResponse.candidates != null && apiResponse.candidates.Any())
{
var rawText = apiResponse.candidates[0].content.parts[0].text;
var commandJson = ExtractJsonFromString(rawText);
if (string.IsNullOrEmpty(commandJson))
{
Debug.LogError($"[GeminiApiClient] Could not extract valid JSON from the LLM response text: \n{rawText}");
return;
}
chatHistory.Add(new ChatEntry { role = "model", content = commandJson });
SessionManager.SaveChatHistory(chatHistory);
CommandResponse commandResponse;
try
{
commandResponse = commandJson.FromJson();
}
catch (Exception exception)
{
Debug.LogException(exception);
return;
}
if (commandResponse is { commands: not null })
{
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}");
}
}
private void LoadSettings()
{
if (_settings) return;
var guids = AssetDatabase.FindAssets("t:MCPSettings");
if (guids.Length == 0) return;
var path = AssetDatabase.GUIDToAssetPath(guids[0]);
_settings = AssetDatabase.LoadAssetAtPath(path);
}
private void LoadSystemPrompt()
{
if (!string.IsNullOrEmpty(_systemPrompt)) return;
var guids = AssetDatabase.FindAssets("MCP_SystemPrompt");
if (guids.Length == 0) return;
var path = AssetDatabase.GUIDToAssetPath(guids[0]);
_systemPrompt = File.ReadAllText(path);
}
private static string ExtractJsonFromString(string text)
{
if (string.IsNullOrWhiteSpace(text)) return null;
var firstBrace = text.IndexOf('{');
var lastBrace = text.LastIndexOf('}');
if (firstBrace == -1 || lastBrace == -1 || lastBrace < firstBrace)
{
return null;
}
return text.Substring(firstBrace, lastBrace - firstBrace + 1);
}
private string GetAuthToken()
{
if (!_settings) 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 (Exception e)
{
Debug.LogError($"[GeminiApiClient] Exception while getting auth token: {e.Message}");
return null;
}
}
private string GetWorkingContext()
{
var workingContext = SessionManager.LoadWorkingContext();
var systemPromptWithContext = new StringBuilder(_systemPrompt);
systemPromptWithContext.AppendLine("\n\n## CURRENT WORKING CONTEXT");
systemPromptWithContext.AppendLine("This is your short-term memory. Use the data here before asking for it again.");
systemPromptWithContext.AppendLine("```json");
systemPromptWithContext.AppendLine(workingContext.ToString(Newtonsoft.Json.Formatting.Indented));
systemPromptWithContext.AppendLine("```");
return systemPromptWithContext.ToString();
}
}
}