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:
MelekiandClaude Opus 4.6 committed 2026-03-19 08:01:49 +01:00
1 parent f3069cedf7
commit 3e19da4d48
12 files changed
+428 -487

No files matched your search

+31 -79
View File
@@ -86,7 +86,12 @@ public class ChatService
var toolChoice = hasTools ? "any" : null;
var toolDefs = hasTools
? _toolService.GetToolDefinitions(includeFileTools: hasWorkspace, includeMemory: includeMemory, includeUserChoice: agent.AllowUserChoice, includeRemote: hasRemoteConnection, includeGitHubSearch: includeGitHubSearch, includeSerperSearch: includeSerperSearch)
? _toolService.GetToolDefinitions(new ToolDefinitionOptions
{
IncludeFileTools = hasWorkspace, IncludeMemory = includeMemory,
IncludeUserChoice = agent.AllowUserChoice, IncludeRemote = hasRemoteConnection,
IncludeGitHubSearch = includeGitHubSearch, IncludeSerperSearch = includeSerperSearch
})
: Array.Empty<object>();
var toolNames = toolDefs.Length > 0
? JsonSerializer.Serialize(toolDefs.Select(td =>
@@ -370,21 +375,7 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop?
? (object)new { type = "tool_use", id = msg.ToolCallId, name = msg.ToolName, input = inputEl, thought_signature = msg.ThoughtSignature }
: new { type = "tool_use", id = msg.ToolCallId, name = msg.ToolName, input = inputEl };
var lastAssistant = history.Count > 0 ? history[history.Count - 1] : null;
if (lastAssistant is Dictionary<string, object> dict &&
dict.TryGetValue("role", out var role) && role.ToString() == "assistant" &&
dict.TryGetValue("content", out var existingContent) && existingContent is List<object> contentList)
{
contentList.Add(toolUseBlock);
}
else
{
history.Add(new Dictionary<string, object>
{
["role"] = "assistant",
["content"] = new List<object> { toolUseBlock }
});
}
AppendToLastMessage(history, "assistant", toolUseBlock);
}
else if (msg.Role == "assistant" && msg.Type == "server_tool_use")
{
@@ -393,21 +384,7 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop?
try { useEl = JsonSerializer.Deserialize<JsonElement>(msg.Content ?? "{}"); }
catch { continue; }
var lastAssistant = history.Count > 0 ? history[history.Count - 1] : null;
if (lastAssistant is Dictionary<string, object> dict &&
dict.TryGetValue("role", out var role) && role.ToString() == "assistant" &&
dict.TryGetValue("content", out var existingContent) && existingContent is List<object> contentList)
{
contentList.Add(useEl);
}
else
{
history.Add(new Dictionary<string, object>
{
["role"] = "assistant",
["content"] = new List<object> { useEl }
});
}
AppendToLastMessage(history, "assistant", useEl);
}
else if (msg.Role == "assistant" && msg.Type == "server_tool_result")
{
@@ -417,21 +394,7 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop?
try { resultEl = JsonSerializer.Deserialize<JsonElement>(msg.Content ?? "{}"); }
catch { continue; }
var lastAssistant = history.Count > 0 ? history[history.Count - 1] : null;
if (lastAssistant is Dictionary<string, object> dict &&
dict.TryGetValue("role", out var role) && role.ToString() == "assistant" &&
dict.TryGetValue("content", out var existingContent) && existingContent is List<object> contentList)
{
contentList.Add(resultEl);
}
else
{
history.Add(new Dictionary<string, object>
{
["role"] = "assistant",
["content"] = new List<object> { resultEl }
});
}
AppendToLastMessage(history, "assistant", resultEl);
}
else if (msg.Role == "assistant" && msg.Type == "web_search_result")
{
@@ -445,42 +408,12 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop?
// Diese überspringen, da sie kein gültiger web_search_tool_result-Block sind
if (resultEl.ValueKind == JsonValueKind.Array) continue;
var lastAssistant = history.Count > 0 ? history[history.Count - 1] : null;
if (lastAssistant is Dictionary<string, object> dict &&
dict.TryGetValue("role", out var role) && role.ToString() == "assistant" &&
dict.TryGetValue("content", out var existingContent) && existingContent is List<object> contentList)
{
contentList.Add(resultEl);
}
else
{
history.Add(new Dictionary<string, object>
{
["role"] = "assistant",
["content"] = new List<object> { resultEl }
});
}
AppendToLastMessage(history, "assistant", resultEl);
}
else if (msg.Role == "user" && msg.Type == "tool_result")
{
var lastUser = history.Count > 0 ? history[history.Count - 1] : null;
if (lastUser is Dictionary<string, object> dict &&
dict.TryGetValue("role", out var role) && role.ToString() == "user" &&
dict.TryGetValue("content", out var existingContent) && existingContent is List<object> contentList)
{
contentList.Add(new { type = "tool_result", tool_use_id = msg.ToolCallId, content = SseHelper.TruncateToolResult(msg.Content ?? ""), is_error = msg.IsError });
}
else
{
history.Add(new Dictionary<string, object>
{
["role"] = "user",
["content"] = new List<object>
{
new { type = "tool_result", tool_use_id = msg.ToolCallId, content = SseHelper.TruncateToolResult(msg.Content ?? ""), is_error = msg.IsError }
}
});
}
var toolResultBlock = new { type = "tool_result", tool_use_id = msg.ToolCallId, content = SseHelper.TruncateToolResult(msg.Content ?? ""), is_error = msg.IsError };
AppendToLastMessage(history, "user", toolResultBlock);
}
}
}
@@ -630,6 +563,25 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop?
return history;
}
private static void AppendToLastMessage(List<object> history, string role, object block)
{
var last = history.Count > 0 ? history[history.Count - 1] : null;
if (last is Dictionary<string, object> dict &&
dict.TryGetValue("role", out var r) && r.ToString() == role &&
dict.TryGetValue("content", out var existing) && existing is List<object> contentList)
{
contentList.Add(block);
}
else
{
history.Add(new Dictionary<string, object>
{
["role"] = role,
["content"] = new List<object> { block }
});
}
}
/// <summary>
/// Entfernt verwaiste web_search-Blöcke aus assistant-Messages:
/// - server_tool_use ohne zugehörigen web_search_tool_result (z.B. alte DB-Einträge)