Web-Suche (Agent-Toggle mit Versionswahl), Admin-only Backup, Haiku 4.5

- Agent: Web-Suche als Dropdown (Deaktiviert / 2025 / 2026) statt bool
- AnthropicService: web_search Tool dynamisch nach Version einfügen
- Stream: server_tool_use + web_search_tool_result Events verarbeiten
- Chat-Frontend: web_search_start/end Events mit Suchindikator
- Dashboard: Badge zeigt Web-Suche mit Jahresversion
- UserRecord: IsAdmin Feld, DB-Migration is_admin Spalte
- AuthMiddleware: IsAdmin aus DB laden und in Context setzen
- BackupEndpoint: Admin-Check (403 für Nicht-Admins)
- AuthEndpoint: isAdmin in Login/Me Response
- Frontend: Backup-Section nur für Admins sichtbar
- Haiku 4.5 als Model-Option (Agent-Dropdown + ChatEndpoint)
- TODO: Cronjobs, Haiku ergänzt
This commit is contained in:
Meleki committed 2026-03-02 14:05:59 +01:00
1 parent 485f47b29a
commit 8039b47dec
19 files changed
+155 -20

No files matched your search

+50 -2
View File
@@ -39,6 +39,7 @@ public class AnthropicService
string sessionId = null,
string agentId = null,
bool useMemory = true,
string webSearchVersion = null,
int maxIterations = 100,
string toolChoice = "any",
CancellationToken cancellationToken = default)
@@ -75,10 +76,17 @@ public class AnthropicService
var hasWorkspace = !string.IsNullOrEmpty(workspace);
var includeMemory = useMemory && _memoryService != null && !string.IsNullOrEmpty(agentId);
var hasTools = hasWorkspace || includeMemory;
var hasWebSearch = !string.IsNullOrEmpty(webSearchVersion);
if (hasWorkspace || includeMemory)
if (hasTools || hasWebSearch)
{
payload["tools"] = _toolService.GetToolDefinitions(includeFileTools: hasWorkspace, includeMemory: includeMemory);
var tools = new List<object>();
if (hasTools)
tools.AddRange(_toolService.GetToolDefinitions(includeFileTools: hasWorkspace, includeMemory: includeMemory));
if (hasWebSearch)
tools.Add(new { type = webSearchVersion, name = "web_search", max_uses = 5 });
payload["tools"] = tools;
// "any" erzwingt Tool-Nutzung, aber nur in der ersten Iteration,
// damit Claude nicht endlos Tools aufruft wenn die Aufgabe erledigt ist.
var effectiveChoice = (toolChoice == "any" && iterations == 1) ? "any" : "auto";
@@ -257,6 +265,7 @@ public class AnthropicService
var currentTextBlock = new StringBuilder();
var blockTypes = new Dictionary<int, string>();
var webSearchResultBlocks = new Dictionary<int, object>();
var executedTools = new List<ToolResult>();
string line;
@@ -313,6 +322,24 @@ public class AnthropicService
currentToolInputJson.Clear();
await WriteSseEvent(response, "tool_start", new { name = currentToolName, id = currentToolId });
}
else if (cbType == "server_tool_use")
{
if (currentTextBlock.Length > 0)
{
assistantContent.Add(new { type = "text", text = currentTextBlock.ToString() });
currentTextBlock.Clear();
}
currentToolId = cb.GetProperty("id").GetString() ?? "";
currentToolName = cb.GetProperty("name").GetString() ?? "";
currentToolInputJson.Clear();
await WriteSseEvent(response, "web_search_start", new { name = currentToolName, id = currentToolId });
}
else if (cbType == "web_search_tool_result")
{
// Gesamten Block als JSON speichern für die Conversation-History
var rawBlock = JsonSerializer.Deserialize<JsonElement>(cb.GetRawText());
webSearchResultBlocks[idx] = rawBlock;
}
break;
}
@@ -379,6 +406,27 @@ public class AnthropicService
isError
});
}
else if (bt == "server_tool_use")
{
// Server-side tool (Web Search) – Anthropic führt es aus, wir speichern nur die Anfrage
JsonElement inputEl;
try { inputEl = JsonSerializer.Deserialize<JsonElement>(currentToolInputJson.ToString()); }
catch { inputEl = JsonSerializer.Deserialize<JsonElement>("{}"); }
assistantContent.Add(new
{
type = "server_tool_use",
id = currentToolId,
name = currentToolName,
input = inputEl
});
}
else if (bt == "web_search_tool_result")
{
// Web-Search-Ergebnis von Anthropic – als Raw-JSON in assistantContent aufnehmen
assistantContent.Add(webSearchResultBlocks[idx]);
await WriteSseEvent(response, "web_search_end", new { id = currentToolId });
}
}
break;
}