using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace Gott.Services; public class ToolService { public object[] GetToolDefinitions(bool includeFileTools = false, bool includeMemory = false) { var allTools = new List(); if (includeFileTools) allTools.AddRange(GetFileTools()); if (includeMemory) allTools.AddRange(GetMemoryToolDefinitions()); return allTools.ToArray(); } private object[] GetFileTools() { return new object[] { new { name = "read_file", description = "Liest den Inhalt einer Datei. Gibt den Textinhalt zurück.", input_schema = new { type = "object", properties = new Dictionary { ["path"] = new { type = "string", description = "Relativer Dateipfad" } }, required = new[] { "path" } } }, new { name = "write_file", description = "Erstellt oder überschreibt eine Datei mit dem angegebenen Inhalt.", input_schema = new { type = "object", properties = new Dictionary { ["path"] = new { type = "string", description = "Relativer Dateipfad" }, ["content"] = new { type = "string", description = "Dateiinhalt" } }, required = new[] { "path", "content" } } }, new { name = "edit_file", description = "Ersetzt einen exakten Textabschnitt in einer Datei. oldText muss exakt matchen.", input_schema = new { type = "object", properties = new Dictionary { ["path"] = new { type = "string", description = "Relativer Dateipfad" }, ["old_text"] = new { type = "string", description = "Exakter Text der ersetzt werden soll" }, ["new_text"] = new { type = "string", description = "Neuer Text" } }, required = new[] { "path", "old_text", "new_text" } } }, new { name = "list_files", description = "Listet Dateien und Ordner in einem Verzeichnis auf.", input_schema = new { type = "object", properties = new Dictionary { ["path"] = new { type = "string", description = "Relativer Verzeichnispfad (leer = Root)" } }, required = Array.Empty() } }, new { name = "delete_file", description = "Löscht eine Datei oder ein leeres Verzeichnis.", input_schema = new { type = "object", properties = new Dictionary { ["path"] = new { type = "string", description = "Relativer Pfad zur Datei oder zum Verzeichnis" } }, required = new[] { "path" } } }, new { name = "execute", description = "Führt einen Shell-Befehl im Workspace-Verzeichnis aus. Gibt stdout und stderr zurück. Timeout: 180 Sekunden.", input_schema = new { type = "object", properties = new Dictionary { ["command"] = new { type = "string", description = "Shell-Befehl zum Ausführen" } }, required = new[] { "command" } } } }; } private object[] GetMemoryToolDefinitions() { return new object[] { new { name = "memory_read", description = "Liest eine Memory-Datei des Agents. Nutze dies um gespeicherte Erinnerungen, Notizen oder Kontextinformationen abzurufen.", input_schema = new { type = "object", properties = new Dictionary { ["path"] = new { type = "string", description = "Relativer Pfad zur Memory-Datei (z.B. 'MEMORY.md' oder 'memory/2025-01-15.md')" } }, required = new[] { "path" } } }, new { name = "memory_write", description = "Schreibt oder überschreibt eine Memory-Datei. Nutze dies um wichtige Informationen, Entscheidungen oder Präferenzen dauerhaft zu speichern.", input_schema = new { type = "object", properties = new Dictionary { ["path"] = new { type = "string", description = "Relativer Pfad zur Memory-Datei (z.B. 'MEMORY.md' oder 'memory/2025-01-15.md')" }, ["content"] = new { type = "string", description = "Inhalt der Datei" }, ["append"] = new { type = "boolean", description = "Wenn true, wird der Inhalt angehängt statt überschrieben (Default: false)" } }, required = new[] { "path", "content" } } }, new { name = "memory_list", description = "Listet alle Memory-Dateien des Agents auf.", input_schema = new { type = "object", properties = new Dictionary { ["path"] = new { type = "string", description = "Optionaler Unterpfad zum Auflisten (leer = Root)" } }, required = Array.Empty() } } }; } public async Task ExecuteTool(string name, JsonElement input, string workspace, MemoryService memoryService = null, string agentId = null) { switch (name) { case "read_file": { if (string.IsNullOrEmpty(workspace)) return "Fehler: Kein Workspace konfiguriert"; var path = ResolvePath(input.GetProperty("path").GetString() ?? "", workspace); if (!File.Exists(path)) return $"Fehler: Datei nicht gefunden: {Path.GetRelativePath(workspace, path)}"; return File.ReadAllText(path); } case "write_file": { if (string.IsNullOrEmpty(workspace)) return "Fehler: Kein Workspace konfiguriert"; var path = ResolvePath(input.GetProperty("path").GetString() ?? "", workspace); var dir = Path.GetDirectoryName(path); if (dir != null) Directory.CreateDirectory(dir); var content = input.GetProperty("content").GetString() ?? ""; File.WriteAllText(path, content); return $"Datei geschrieben: {Path.GetRelativePath(workspace, path)} ({content.Length} Zeichen)"; } case "edit_file": { if (string.IsNullOrEmpty(workspace)) return "Fehler: Kein Workspace konfiguriert"; var path = ResolvePath(input.GetProperty("path").GetString() ?? "", workspace); if (!File.Exists(path)) return $"Fehler: Datei nicht gefunden: {Path.GetRelativePath(workspace, path)}"; var fileContent = File.ReadAllText(path); var oldText = input.GetProperty("old_text").GetString() ?? ""; var newText = input.GetProperty("new_text").GetString() ?? ""; if (!fileContent.Contains(oldText)) return "Fehler: old_text nicht in der Datei gefunden. Exakter Match nötig."; fileContent = fileContent.Replace(oldText, newText); File.WriteAllText(path, fileContent); return $"Datei bearbeitet: {Path.GetRelativePath(workspace, path)}"; } case "list_files": { if (string.IsNullOrEmpty(workspace)) return "Fehler: Kein Workspace konfiguriert"; var path = ResolvePath(input.TryGetProperty("path", out var p) ? p.GetString() ?? "" : "", workspace); if (!Directory.Exists(path)) return $"Fehler: Verzeichnis nicht gefunden: {Path.GetRelativePath(workspace, path)}"; var entries = new List(); foreach (var dir in Directory.GetDirectories(path)) entries.Add(Path.GetRelativePath(workspace, dir) + "/"); foreach (var file in Directory.GetFiles(path)) entries.Add(Path.GetRelativePath(workspace, file)); return entries.Count == 0 ? "(leer)" : string.Join("\n", entries); } case "delete_file": { if (string.IsNullOrEmpty(workspace)) return "Fehler: Kein Workspace konfiguriert"; var path = ResolvePath(input.GetProperty("path").GetString() ?? "", workspace); if (File.Exists(path)) { File.Delete(path); return $"Datei gelöscht: {Path.GetRelativePath(workspace, path)}"; } if (Directory.Exists(path)) { Directory.Delete(path, recursive: false); return $"Verzeichnis gelöscht: {Path.GetRelativePath(workspace, path)}"; } return $"Fehler: Nicht gefunden: {Path.GetRelativePath(workspace, path)}"; } case "execute": { if (string.IsNullOrEmpty(workspace)) return "Fehler: Kein Workspace konfiguriert"; var command = input.GetProperty("command").GetString() ?? ""; var psi = new ProcessStartInfo { FileName = "/bin/bash", WorkingDirectory = workspace, RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; using var proc = Process.Start(psi); if (proc == null) return "Fehler: Prozess konnte nicht gestartet werden"; await proc.StandardInput.WriteAsync(command); proc.StandardInput.Close(); var cts = new CancellationTokenSource(TimeSpan.FromSeconds(180)); try { await proc.WaitForExitAsync(cts.Token); } catch (OperationCanceledException) { proc.Kill(entireProcessTree: true); return "Fehler: Timeout nach 180 Sekunden - Prozess abgebrochen"; } var stdout = await proc.StandardOutput.ReadToEndAsync(); var stderr = await proc.StandardError.ReadToEndAsync(); var result = new StringBuilder(); if (!string.IsNullOrEmpty(stdout)) result.Append(stdout); if (!string.IsNullOrEmpty(stderr)) { if (result.Length > 0) result.Append("\n--- stderr ---\n"); result.Append(stderr); } if (result.Length == 0) result.Append($"(exit code {proc.ExitCode})"); else if (proc.ExitCode != 0) result.Append($"\n(exit code {proc.ExitCode})"); return result.ToString(); } case "memory_read": { if (memoryService == null || string.IsNullOrEmpty(agentId)) return "Fehler: Memory-Service nicht verfügbar"; var path = input.GetProperty("path").GetString() ?? ""; var content = memoryService.ReadMemoryFile(agentId, path); if (content == null) return $"Datei nicht gefunden: {path}"; return content; } case "memory_write": { if (memoryService == null || string.IsNullOrEmpty(agentId)) return "Fehler: Memory-Service nicht verfügbar"; var path = input.GetProperty("path").GetString() ?? ""; var content = input.GetProperty("content").GetString() ?? ""; var append = input.TryGetProperty("append", out var a) && a.GetBoolean(); if (append) memoryService.AppendMemoryFile(agentId, path, content); else memoryService.WriteMemoryFile(agentId, path, content); return $"Memory-Datei {(append ? "erweitert" : "geschrieben")}: {path} ({content.Length} Zeichen)"; } case "memory_list": { if (memoryService == null || string.IsNullOrEmpty(agentId)) return "Fehler: Memory-Service nicht verfügbar"; var subPath = input.TryGetProperty("path", out var p) ? p.GetString() ?? "" : ""; var files = memoryService.ListMemoryFiles(agentId, subPath); return files.Count == 0 ? "(keine Memory-Dateien vorhanden)" : string.Join("\n", files); } default: return $"Fehler: Unbekanntes Tool: {name}"; } } private string ResolvePath(string relativePath, string workspace) { var full = Path.GetFullPath(Path.Combine(workspace, relativePath ?? "")); if (!full.StartsWith(workspace)) throw new Exception("Zugriff außerhalb des Workspace nicht erlaubt"); return full; } }