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() { 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: 30 Sekunden.", input_schema = new { type = "object", properties = new Dictionary { ["command"] = new { type = "string", description = "Shell-Befehl zum Ausführen" } }, required = new[] { "command" } } } }; } public async Task ExecuteTool(string name, JsonElement input, string workspace) { switch (name) { case "read_file": { 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": { 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": { 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": { 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": { 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": { 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(30)); try { await proc.WaitForExitAsync(cts.Token); } catch (OperationCanceledException) { proc.Kill(entireProcessTree: true); return "Fehler: Timeout nach 30 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(); } 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; } }