refactor: Backend-Deduplizierung – Parameter-Objekte, Shared Helpers, KeyCheckDispatcher
- ToolDefinitionOptions + ToolExecutionContext ersetzen lange Parameterlisten in ToolService - AppendToLastMessage-Helper in ChatService eliminiert 5x duplizierte Logik - OpenAiService: ResolveProviderConfig + BuildApiMessages + ConvertToOpenAiTools statt doppeltem Code - Tool-Definitionen aus einer Quelle (Anthropic→OpenAI Konvertierung statt hardcodierter Liste) - KeyCheckDispatcher zentralisiert Key-Check-Dispatch (ApiKeysEndpoint, KeyScanner, GitHubEventScanner) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
f3069cedf7
commit
3e19da4d48
12 files changed
+428
-487
No files matched your search
+111
-80
@@ -15,6 +15,37 @@ using Microsoft.Extensions.FileSystemGlobbing.Abstractions;
|
||||
|
||||
namespace Gott.Services;
|
||||
|
||||
public class ToolDefinitionOptions
|
||||
{
|
||||
public bool IncludeFileTools { get; set; }
|
||||
public bool IncludeMemory { get; set; }
|
||||
public bool IncludeUserChoice { get; set; } = true;
|
||||
public bool IncludeRemote { get; set; }
|
||||
public bool IncludeMemorySearch { get; set; }
|
||||
public bool IncludeSendToSession { get; set; }
|
||||
public bool IncludeGitHubSearch { get; set; }
|
||||
public bool IncludeSerperSearch { get; set; }
|
||||
}
|
||||
|
||||
public class ToolExecutionContext
|
||||
{
|
||||
public string Workspace { get; set; }
|
||||
public MemoryService MemoryService { get; set; }
|
||||
public string AgentId { get; set; }
|
||||
public CancellationToken CancellationToken { get; set; }
|
||||
public RemoteConnectionManager RemoteMgr { get; set; }
|
||||
public string SessionId { get; set; }
|
||||
public VectorSearchService VectorSearch { get; set; }
|
||||
public EmbeddingBackgroundService EmbeddingBg { get; set; }
|
||||
public DatabaseService Db { get; set; }
|
||||
public ChatService ChatService { get; set; }
|
||||
public AnthropicService AnthropicService { get; set; }
|
||||
public OpenAiService OpenAiService { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public TelegramService TelegramService { get; set; }
|
||||
public string ApiToken { get; set; }
|
||||
}
|
||||
|
||||
public class ToolService
|
||||
{
|
||||
private readonly ServerSettingsService _settings;
|
||||
@@ -63,24 +94,24 @@ public class ToolService
|
||||
return filtered.ToArray();
|
||||
}
|
||||
|
||||
public object[] GetToolDefinitions(bool includeFileTools = false, bool includeMemory = false, bool includeUserChoice = true, bool includeRemote = false, bool includeMemorySearch = false, bool includeSendToSession = false, bool includeGitHubSearch = false, bool includeSerperSearch = false)
|
||||
public object[] GetToolDefinitions(ToolDefinitionOptions opts)
|
||||
{
|
||||
var allTools = new List<object>();
|
||||
if (includeUserChoice)
|
||||
if (opts.IncludeUserChoice)
|
||||
allTools.Add(GetAskUserChoiceTool());
|
||||
if (includeFileTools)
|
||||
if (opts.IncludeFileTools)
|
||||
allTools.AddRange(GetFileTools());
|
||||
if (includeMemory)
|
||||
if (opts.IncludeMemory)
|
||||
allTools.AddRange(GetMemoryToolDefinitions());
|
||||
if (includeMemorySearch)
|
||||
if (opts.IncludeMemorySearch)
|
||||
allTools.Add(GetMemorySearchTool());
|
||||
if (includeRemote)
|
||||
if (opts.IncludeRemote)
|
||||
allTools.Add(GetRemoteExecuteTool());
|
||||
if (includeSendToSession)
|
||||
if (opts.IncludeSendToSession)
|
||||
allTools.Add(GetSendToSessionTool());
|
||||
if (includeGitHubSearch)
|
||||
if (opts.IncludeGitHubSearch)
|
||||
allTools.Add(GetGitHubCodeSearchTool());
|
||||
if (includeSerperSearch)
|
||||
if (opts.IncludeSerperSearch)
|
||||
allTools.Add(GetSerperWebSearchTool());
|
||||
return allTools.ToArray();
|
||||
}
|
||||
@@ -390,17 +421,17 @@ public class ToolService
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<string> ExecuteTool(string name, JsonElement input, string workspace, MemoryService memoryService = null, string agentId = null, CancellationToken cancellationToken = default, RemoteConnectionManager remoteMgr = null, string sessionId = null, VectorSearchService vectorSearch = null, EmbeddingBackgroundService embeddingBg = null, DatabaseService db = null, ChatService chatService = null, AnthropicService anthropicService = null, OpenAiService openAiService = null, string userId = null, TelegramService telegramService = null, string apiToken = null)
|
||||
public async Task<string> ExecuteTool(string name, JsonElement input, ToolExecutionContext ctx)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case "read_file":
|
||||
{
|
||||
if (string.IsNullOrEmpty(workspace))
|
||||
if (string.IsNullOrEmpty(ctx.Workspace))
|
||||
return "Fehler: Kein Workspace konfiguriert";
|
||||
var path = ResolvePath(input.GetProperty("path").GetString() ?? "", workspace);
|
||||
var path = ResolvePath(input.GetProperty("path").GetString() ?? "", ctx.Workspace);
|
||||
if (!File.Exists(path))
|
||||
return $"Fehler: Datei nicht gefunden: {WorkspaceHelper.GetRelativePath(workspace, path)}";
|
||||
return $"Fehler: Datei nicht gefunden: {WorkspaceHelper.GetRelativePath(ctx.Workspace, path)}";
|
||||
var allLines = File.ReadAllLines(path);
|
||||
var totalLines = allLines.Length;
|
||||
var offset = input.TryGetProperty("offset", out var offsetProp) ? Math.Max(1, offsetProp.GetInt32()) : 1;
|
||||
@@ -417,22 +448,22 @@ public class ToolService
|
||||
}
|
||||
case "write_file":
|
||||
{
|
||||
if (string.IsNullOrEmpty(workspace))
|
||||
if (string.IsNullOrEmpty(ctx.Workspace))
|
||||
return "Fehler: Kein Workspace konfiguriert";
|
||||
var path = ResolvePath(input.GetProperty("path").GetString() ?? "", workspace);
|
||||
var path = ResolvePath(input.GetProperty("path").GetString() ?? "", ctx.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: {WorkspaceHelper.GetRelativePath(workspace, path)} ({content.Length} Zeichen)";
|
||||
return $"Datei geschrieben: {WorkspaceHelper.GetRelativePath(ctx.Workspace, path)} ({content.Length} Zeichen)";
|
||||
}
|
||||
case "edit_file":
|
||||
{
|
||||
if (string.IsNullOrEmpty(workspace))
|
||||
if (string.IsNullOrEmpty(ctx.Workspace))
|
||||
return "Fehler: Kein Workspace konfiguriert";
|
||||
var path = ResolvePath(input.GetProperty("path").GetString() ?? "", workspace);
|
||||
var path = ResolvePath(input.GetProperty("path").GetString() ?? "", ctx.Workspace);
|
||||
if (!File.Exists(path))
|
||||
return $"Fehler: Datei nicht gefunden: {WorkspaceHelper.GetRelativePath(workspace, path)}";
|
||||
return $"Fehler: Datei nicht gefunden: {WorkspaceHelper.GetRelativePath(ctx.Workspace, path)}";
|
||||
var fileContent = File.ReadAllText(path);
|
||||
var oldText = input.GetProperty("old_text").GetString() ?? "";
|
||||
var newText = input.GetProperty("new_text").GetString() ?? "";
|
||||
@@ -440,42 +471,42 @@ public class ToolService
|
||||
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: {WorkspaceHelper.GetRelativePath(workspace, path)}";
|
||||
return $"Datei bearbeitet: {WorkspaceHelper.GetRelativePath(ctx.Workspace, path)}";
|
||||
}
|
||||
case "list_files":
|
||||
{
|
||||
if (string.IsNullOrEmpty(workspace))
|
||||
if (string.IsNullOrEmpty(ctx.Workspace))
|
||||
return "Fehler: Kein Workspace konfiguriert";
|
||||
var path = ResolvePath(input.TryGetProperty("path", out var p) ? p.GetString() ?? "" : "", workspace);
|
||||
var path = ResolvePath(input.TryGetProperty("path", out var p) ? p.GetString() ?? "" : "", ctx.Workspace);
|
||||
if (!Directory.Exists(path))
|
||||
return $"Fehler: Verzeichnis nicht gefunden: {WorkspaceHelper.GetRelativePath(workspace, path)}";
|
||||
return $"Fehler: Verzeichnis nicht gefunden: {WorkspaceHelper.GetRelativePath(ctx.Workspace, path)}";
|
||||
var entries = new List<string>();
|
||||
foreach (var dir in Directory.GetDirectories(path))
|
||||
entries.Add(WorkspaceHelper.GetRelativePath(workspace, dir) + "/");
|
||||
entries.Add(WorkspaceHelper.GetRelativePath(ctx.Workspace, dir) + "/");
|
||||
foreach (var file in Directory.GetFiles(path))
|
||||
entries.Add(WorkspaceHelper.GetRelativePath(workspace, file));
|
||||
entries.Add(WorkspaceHelper.GetRelativePath(ctx.Workspace, file));
|
||||
return entries.Count == 0 ? "(leer)" : string.Join("\n", entries);
|
||||
}
|
||||
case "delete_file":
|
||||
{
|
||||
if (string.IsNullOrEmpty(workspace))
|
||||
if (string.IsNullOrEmpty(ctx.Workspace))
|
||||
return "Fehler: Kein Workspace konfiguriert";
|
||||
var path = ResolvePath(input.GetProperty("path").GetString() ?? "", workspace);
|
||||
var path = ResolvePath(input.GetProperty("path").GetString() ?? "", ctx.Workspace);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
return $"Datei gelöscht: {WorkspaceHelper.GetRelativePath(workspace, path)}";
|
||||
return $"Datei gelöscht: {WorkspaceHelper.GetRelativePath(ctx.Workspace, path)}";
|
||||
}
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
Directory.Delete(path, recursive: false);
|
||||
return $"Verzeichnis gelöscht: {WorkspaceHelper.GetRelativePath(workspace, path)}";
|
||||
return $"Verzeichnis gelöscht: {WorkspaceHelper.GetRelativePath(ctx.Workspace, path)}";
|
||||
}
|
||||
return $"Fehler: Nicht gefunden: {WorkspaceHelper.GetRelativePath(workspace, path)}";
|
||||
return $"Fehler: Nicht gefunden: {WorkspaceHelper.GetRelativePath(ctx.Workspace, path)}";
|
||||
}
|
||||
case "execute":
|
||||
{
|
||||
if (string.IsNullOrEmpty(workspace))
|
||||
if (string.IsNullOrEmpty(ctx.Workspace))
|
||||
return "Fehler: Kein Workspace konfiguriert";
|
||||
var command = input.GetProperty("command").GetString() ?? "";
|
||||
// Unter Windows: Befehl in temp .cmd-Datei schreiben und mit /c ausführen.
|
||||
@@ -484,7 +515,7 @@ public class ToolService
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = WorkspaceHelper.ShellExecutable,
|
||||
WorkingDirectory = workspace,
|
||||
WorkingDirectory = ctx.Workspace,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
@@ -513,7 +544,7 @@ public class ToolService
|
||||
}
|
||||
// Timeout UND externen CancellationToken verknüpfen
|
||||
var timeoutSeconds = ExecuteTimeoutSeconds;
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ctx.CancellationToken);
|
||||
cts.CancelAfter(TimeSpan.FromSeconds(timeoutSeconds));
|
||||
// stdout/stderr VOR WaitForExit lesen – verhindert Deadlock
|
||||
// wenn Pipe-Buffer volllaufen (Windows: nur 4KB!)
|
||||
@@ -528,7 +559,7 @@ public class ToolService
|
||||
proc.Kill(entireProcessTree: true);
|
||||
if (scriptPath != null) try { File.Delete(scriptPath); } catch { }
|
||||
// Wenn der externe Token abgebrochen wurde, weiterwerfen
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ctx.CancellationToken.ThrowIfCancellationRequested();
|
||||
return $"Fehler: Timeout nach {timeoutSeconds} Sekunden - Prozess abgebrochen";
|
||||
}
|
||||
if (scriptPath != null) try { File.Delete(scriptPath); } catch { }
|
||||
@@ -547,7 +578,7 @@ public class ToolService
|
||||
}
|
||||
case "background_execute":
|
||||
{
|
||||
if (string.IsNullOrEmpty(workspace))
|
||||
if (string.IsNullOrEmpty(ctx.Workspace))
|
||||
return "Fehler: Kein Workspace konfiguriert";
|
||||
var command = input.GetProperty("command").GetString() ?? "";
|
||||
// Befehl in temporäre Script-Datei schreiben und losgelöst starten.
|
||||
@@ -572,7 +603,7 @@ public class ToolService
|
||||
{
|
||||
FileName = WorkspaceHelper.IsWindows ? "cmd.exe" : scriptPath,
|
||||
Arguments = WorkspaceHelper.IsWindows ? $"/c \"{scriptPath}\"" : "",
|
||||
WorkingDirectory = workspace,
|
||||
WorkingDirectory = ctx.Workspace,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
@@ -582,13 +613,13 @@ public class ToolService
|
||||
}
|
||||
case "glob":
|
||||
{
|
||||
if (string.IsNullOrEmpty(workspace))
|
||||
if (string.IsNullOrEmpty(ctx.Workspace))
|
||||
return "Fehler: Kein Workspace konfiguriert";
|
||||
var pattern = input.GetProperty("pattern").GetString() ?? "*";
|
||||
var basePath = input.TryGetProperty("path", out var gp) ? gp.GetString() ?? "" : "";
|
||||
var searchRoot = string.IsNullOrEmpty(basePath)
|
||||
? workspace
|
||||
: ResolvePath(basePath, workspace);
|
||||
? ctx.Workspace
|
||||
: ResolvePath(basePath, ctx.Workspace);
|
||||
if (!Directory.Exists(searchRoot))
|
||||
return $"Fehler: Verzeichnis nicht gefunden: {basePath}";
|
||||
var matcher = new Matcher();
|
||||
@@ -596,14 +627,14 @@ public class ToolService
|
||||
var dirInfo = new DirectoryInfoWrapper(new DirectoryInfo(searchRoot));
|
||||
var result = matcher.Execute(dirInfo);
|
||||
var matches = result.Files
|
||||
.Select(f => WorkspaceHelper.GetRelativePath(workspace, Path.Combine(searchRoot, f.Path)))
|
||||
.Select(f => WorkspaceHelper.GetRelativePath(ctx.Workspace, Path.Combine(searchRoot, f.Path)))
|
||||
.OrderBy(f => f)
|
||||
.ToList();
|
||||
return matches.Count == 0 ? "(keine Treffer)" : string.Join("\n", matches);
|
||||
}
|
||||
case "grep":
|
||||
{
|
||||
if (string.IsNullOrEmpty(workspace))
|
||||
if (string.IsNullOrEmpty(ctx.Workspace))
|
||||
return "Fehler: Kein Workspace konfiguriert";
|
||||
var pattern = input.GetProperty("pattern").GetString() ?? "";
|
||||
var searchPath = input.TryGetProperty("path", out var sp) ? sp.GetString() ?? "" : "";
|
||||
@@ -614,8 +645,8 @@ public class ToolService
|
||||
try { regex = new Regex(pattern, regexOptions); }
|
||||
catch { regex = new Regex(Regex.Escape(pattern), regexOptions); }
|
||||
var searchRoot = string.IsNullOrEmpty(searchPath)
|
||||
? workspace
|
||||
: ResolvePath(searchPath, workspace);
|
||||
? ctx.Workspace
|
||||
: ResolvePath(searchPath, ctx.Workspace);
|
||||
IEnumerable<string> files;
|
||||
if (File.Exists(searchRoot))
|
||||
files = new[] { searchRoot };
|
||||
@@ -632,7 +663,7 @@ public class ToolService
|
||||
for (var i = 0; i < lines.Length; i++)
|
||||
{
|
||||
if (regex.IsMatch(lines[i]))
|
||||
results.Add($"{WorkspaceHelper.GetRelativePath(workspace, file)}:{i + 1}: {lines[i].Trim()}");
|
||||
results.Add($"{WorkspaceHelper.GetRelativePath(ctx.Workspace, file)}:{i + 1}: {lines[i].Trim()}");
|
||||
}
|
||||
}
|
||||
if (results.Count == 0) return "(keine Treffer)";
|
||||
@@ -657,51 +688,51 @@ public class ToolService
|
||||
}
|
||||
case "memory_read":
|
||||
{
|
||||
if (memoryService == null || string.IsNullOrEmpty(agentId))
|
||||
if (ctx.MemoryService == null || string.IsNullOrEmpty(ctx.AgentId))
|
||||
return "Fehler: Memory-Service nicht verfügbar";
|
||||
var path = input.GetProperty("path").GetString() ?? "";
|
||||
var content = memoryService.ReadMemoryFile(agentId, path);
|
||||
var content = ctx.MemoryService.ReadMemoryFile(ctx.AgentId, path);
|
||||
if (content == null)
|
||||
return $"Datei nicht gefunden: {path}";
|
||||
return content;
|
||||
}
|
||||
case "memory_write":
|
||||
{
|
||||
if (memoryService == null || string.IsNullOrEmpty(agentId))
|
||||
if (ctx.MemoryService == null || string.IsNullOrEmpty(ctx.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);
|
||||
ctx.MemoryService.AppendMemoryFile(ctx.AgentId, path, content);
|
||||
else
|
||||
memoryService.WriteMemoryFile(agentId, path, content);
|
||||
ctx.MemoryService.WriteMemoryFile(ctx.AgentId, path, content);
|
||||
// Memory-Datei nach Schreiben embedden
|
||||
if (embeddingBg != null)
|
||||
if (ctx.EmbeddingBg != null)
|
||||
{
|
||||
var fullText = memoryService.ReadMemoryFile(agentId, path);
|
||||
var fullText = ctx.MemoryService.ReadMemoryFile(ctx.AgentId, path);
|
||||
if (!string.IsNullOrEmpty(fullText))
|
||||
embeddingBg.EnqueueMemoryFile(agentId, path, fullText);
|
||||
ctx.EmbeddingBg.EnqueueMemoryFile(ctx.AgentId, path, fullText);
|
||||
}
|
||||
return $"Memory-Datei {(append ? "erweitert" : "geschrieben")}: {path} ({content.Length} Zeichen)";
|
||||
}
|
||||
case "memory_list":
|
||||
{
|
||||
if (memoryService == null || string.IsNullOrEmpty(agentId))
|
||||
if (ctx.MemoryService == null || string.IsNullOrEmpty(ctx.AgentId))
|
||||
return "Fehler: Memory-Service nicht verfügbar";
|
||||
var subPath = input.TryGetProperty("path", out var p) ? p.GetString() ?? "" : "";
|
||||
var files = memoryService.ListMemoryFiles(agentId, subPath);
|
||||
var files = ctx.MemoryService.ListMemoryFiles(ctx.AgentId, subPath);
|
||||
return files.Count == 0 ? "(keine Memory-Dateien vorhanden)" : string.Join("\n", files);
|
||||
}
|
||||
case "memory_search":
|
||||
{
|
||||
if (vectorSearch == null)
|
||||
if (ctx.VectorSearch == null)
|
||||
return "Fehler: Semantische Suche nicht verfügbar";
|
||||
var query = input.GetProperty("query").GetString() ?? "";
|
||||
var topN = input.TryGetProperty("top_n", out var tn) ? tn.GetInt32() : 10;
|
||||
if (topN > 30) topN = 30;
|
||||
if (topN < 1) topN = 1;
|
||||
var results = await vectorSearch.SearchAsync(query, agentId, topN);
|
||||
var results = await ctx.VectorSearch.SearchAsync(query, ctx.AgentId, topN);
|
||||
if (results.Count == 0)
|
||||
return "Keine relevanten Ergebnisse gefunden.";
|
||||
var sb = new StringBuilder();
|
||||
@@ -718,13 +749,13 @@ public class ToolService
|
||||
}
|
||||
case "remote_execute":
|
||||
{
|
||||
if (remoteMgr == null || string.IsNullOrEmpty(sessionId))
|
||||
if (ctx.RemoteMgr == null || string.IsNullOrEmpty(ctx.SessionId))
|
||||
return "Fehler: Keine Remote-Verbindung verfügbar";
|
||||
var conn = remoteMgr.GetBySessionId(sessionId);
|
||||
var conn = ctx.RemoteMgr.GetBySessionId(ctx.SessionId);
|
||||
if (conn == null)
|
||||
return "Fehler: Keine Remote-Verbindung für diese Session zugeordnet";
|
||||
var command = input.GetProperty("command").GetString() ?? "";
|
||||
var result = await remoteMgr.ExecuteCommand(conn.Id, command, ExecuteTimeoutSeconds, cancellationToken);
|
||||
var result = await ctx.RemoteMgr.ExecuteCommand(conn.Id, command, ExecuteTimeoutSeconds, ctx.CancellationToken);
|
||||
var sb = new StringBuilder();
|
||||
if (!string.IsNullOrEmpty(result.Output))
|
||||
sb.Append(result.Output);
|
||||
@@ -742,7 +773,7 @@ public class ToolService
|
||||
}
|
||||
case "send_to_session":
|
||||
{
|
||||
if (db == null || chatService == null || (anthropicService == null && openAiService == null))
|
||||
if (ctx.Db == null || ctx.ChatService == null || (ctx.AnthropicService == null && ctx.OpenAiService == null))
|
||||
return "Fehler: send_to_session nicht verfügbar";
|
||||
|
||||
var sessionIdOrName = input.GetProperty("session_id_or_name").GetString() ?? "";
|
||||
@@ -753,62 +784,62 @@ public class ToolService
|
||||
return "Fehler: message fehlt";
|
||||
|
||||
// Ziel-Session per ID oder Name suchen (instanzweit, kein User-Check)
|
||||
var targetSession = await db.GetSession(sessionIdOrName);
|
||||
var targetSession = await ctx.Db.GetSession(sessionIdOrName);
|
||||
if (targetSession == null)
|
||||
targetSession = await db.GetSessionByName(sessionIdOrName);
|
||||
targetSession = await ctx.Db.GetSessionByName(sessionIdOrName);
|
||||
if (targetSession == null)
|
||||
return $"Fehler: Session '{sessionIdOrName}' nicht gefunden";
|
||||
|
||||
var targetSessionId = targetSession.SessionId;
|
||||
if (targetSessionId == sessionId)
|
||||
if (targetSessionId == ctx.SessionId)
|
||||
return "Fehler: Kann keine Nachricht an die eigene Session senden";
|
||||
|
||||
// Context für Ziel-Session vorbereiten (analog zu CronjobService)
|
||||
var ctx = await chatService.PrepareContext(targetSessionId, userId: userId);
|
||||
if (ctx == null)
|
||||
var targetCtx = await ctx.ChatService.PrepareContext(targetSessionId, userId: ctx.UserId);
|
||||
if (targetCtx == null)
|
||||
return $"Fehler: Session '{targetSession.Name}' ist nicht konfiguriert (kein Agent zugewiesen?)";
|
||||
|
||||
// API-Key des Ziel-Agents auflösen (nicht den Key des aufrufenden Agents nutzen)
|
||||
var targetApiKey = await db.GetAgentApiKeyRecord(userId, ctx.AgentId);
|
||||
ctx.ApiToken = targetApiKey?.ApiKey ?? apiToken;
|
||||
var targetApiKey = await ctx.Db.GetAgentApiKeyRecord(ctx.UserId, targetCtx.AgentId);
|
||||
targetCtx.ApiToken = targetApiKey?.ApiKey ?? ctx.ApiToken;
|
||||
|
||||
// History aufbauen – User-Message NICHT in DB speichern (wie Cronjob)
|
||||
// Stattdessen als kontextualisierte Nachricht in die History injizieren
|
||||
var history = await chatService.BuildHistoryFromDb(targetSessionId, promptBlocks: ctx.PromptBlocks);
|
||||
var senderAgent = agentId != null ? await db.GetAgent(agentId) : null;
|
||||
var senderName = senderAgent?.Name ?? agentId ?? "Unbekannter Agent";
|
||||
var history = await ctx.ChatService.BuildHistoryFromDb(targetSessionId, promptBlocks: targetCtx.PromptBlocks);
|
||||
var senderAgent = ctx.AgentId != null ? await ctx.Db.GetAgent(ctx.AgentId) : null;
|
||||
var senderName = senderAgent?.Name ?? ctx.AgentId ?? "Unbekannter Agent";
|
||||
var senderInfo = $"[Nachricht von Agent \"{senderName}\"]: {message}";
|
||||
var userContent = new List<object> { new { type = "text", text = senderInfo } };
|
||||
history.Add(new Dictionary<string, object> { ["role"] = "user", ["content"] = userContent });
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ctx.Provider) && openAiService != null)
|
||||
await openAiService.ChatNonStreaming(ctx, history);
|
||||
if (!string.IsNullOrEmpty(targetCtx.Provider) && ctx.OpenAiService != null)
|
||||
await ctx.OpenAiService.ChatNonStreaming(targetCtx, history);
|
||||
else
|
||||
await anthropicService.ChatNonStreaming(ctx, history);
|
||||
await ctx.AnthropicService.ChatNonStreaming(targetCtx, history);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[send_to_session] ChatNonStreaming Fehler: {ex}");
|
||||
return $"Fehler beim Senden an Session \"{targetSession.Name}\": {ex.Message}";
|
||||
}
|
||||
await db.UpdateSessionTimestamp(targetSessionId);
|
||||
await ctx.Db.UpdateSessionTimestamp(targetSessionId);
|
||||
|
||||
// Telegram-Benachrichtigung senden falls Session einen Bot hat
|
||||
if (telegramService != null)
|
||||
if (ctx.TelegramService != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var updatedSession = await db.GetSession(targetSessionId);
|
||||
var updatedSession = await ctx.Db.GetSession(targetSessionId);
|
||||
if (updatedSession != null && !string.IsNullOrEmpty(updatedSession.TelegramChatId))
|
||||
{
|
||||
var recentMsgs = await db.GetLatestActiveMessages(targetSessionId, 5);
|
||||
var recentMsgs = await ctx.Db.GetLatestActiveMessages(targetSessionId, 5);
|
||||
var lastMsg = recentMsgs
|
||||
.Where(m => m.Role == "assistant" && m.Type == "text")
|
||||
.LastOrDefault();
|
||||
if (lastMsg != null)
|
||||
await telegramService.SendNotification(targetSessionId, lastMsg.Content);
|
||||
await ctx.TelegramService.SendNotification(targetSessionId, lastMsg.Content);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -821,11 +852,11 @@ public class ToolService
|
||||
}
|
||||
case "github_code_search":
|
||||
{
|
||||
return await ExecuteGitHubCodeSearch(input, cancellationToken);
|
||||
return await ExecuteGitHubCodeSearch(input, ctx.CancellationToken);
|
||||
}
|
||||
case "serper_web_search":
|
||||
{
|
||||
return await ExecuteSerperWebSearch(input, cancellationToken);
|
||||
return await ExecuteSerperWebSearch(input, ctx.CancellationToken);
|
||||
}
|
||||
default:
|
||||
return $"Fehler: Unbekanntes Tool: {name}";
|
||||
|
||||
Reference in new issue
Block a user