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:
1 parent
485f47b29a
commit
8039b47dec
19 files changed
+155
-20
No files matched your search
@@ -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;
|
||||
}
|
||||
|
||||
@@ -145,9 +145,20 @@ public class DatabaseService
|
||||
TryAddColumn(conn, "agents", "use_user_info", "INTEGER NOT NULL DEFAULT 1");
|
||||
TryAddColumn(conn, "agents", "use_memory", "INTEGER NOT NULL DEFAULT 1");
|
||||
|
||||
TryAddColumn(conn, "agents", "use_web_search", "INTEGER NOT NULL DEFAULT 0");
|
||||
TryAddColumn(conn, "agents", "web_search_version", "TEXT DEFAULT ''");
|
||||
|
||||
// Migration: use_web_search (bool) → web_search_version (text)
|
||||
try
|
||||
{
|
||||
conn.Execute("UPDATE agents SET web_search_version = 'web_search_20260209' WHERE use_web_search = 1 AND (web_search_version IS NULL OR web_search_version = '')");
|
||||
}
|
||||
catch { /* already migrated */ }
|
||||
|
||||
// User-Zuordnung
|
||||
TryAddColumn(conn, "sessions", "user_id", "TEXT");
|
||||
TryAddColumn(conn, "agents", "user_id", "TEXT");
|
||||
TryAddColumn(conn, "users", "is_admin", "INTEGER NOT NULL DEFAULT 0");
|
||||
|
||||
conn.Execute(@"
|
||||
CREATE TABLE IF NOT EXISTS agent_skills (
|
||||
@@ -189,8 +200,8 @@ public class DatabaseService
|
||||
agent.CreatedAt = now;
|
||||
agent.UpdatedAt = now;
|
||||
await conn.ExecuteAsync(@"
|
||||
INSERT INTO agents (agent_id, user_id, name, avatar, description, model, tool_choice, custom_commands, auto_commit, use_soul, use_identity, use_user_info, use_memory, is_deleted, created_at, updated_at)
|
||||
VALUES (@AgentId, @UserId, @Name, @Avatar, @Description, @Model, @ToolChoice, @CustomCommands, @AutoCommit, @UseSoul, @UseIdentity, @UseUserInfo, @UseMemory, 0, @CreatedAt, @UpdatedAt)
|
||||
INSERT INTO agents (agent_id, user_id, name, avatar, description, model, tool_choice, custom_commands, auto_commit, use_soul, use_identity, use_user_info, use_memory, use_web_search, web_search_version, is_deleted, created_at, updated_at)
|
||||
VALUES (@AgentId, @UserId, @Name, @Avatar, @Description, @Model, @ToolChoice, @CustomCommands, @AutoCommit, @UseSoul, @UseIdentity, @UseUserInfo, @UseMemory, @UseWebSearch, @WebSearchVersion, 0, @CreatedAt, @UpdatedAt)
|
||||
", agent);
|
||||
return agent;
|
||||
}
|
||||
@@ -217,7 +228,7 @@ public class DatabaseService
|
||||
UPDATE agents SET name = @Name, avatar = @Avatar, description = @Description,
|
||||
model = @Model, tool_choice = @ToolChoice,
|
||||
custom_commands = @CustomCommands, auto_commit = @AutoCommit,
|
||||
use_soul = @UseSoul, use_identity = @UseIdentity, use_user_info = @UseUserInfo, use_memory = @UseMemory,
|
||||
use_soul = @UseSoul, use_identity = @UseIdentity, use_user_info = @UseUserInfo, use_memory = @UseMemory, use_web_search = @UseWebSearch, web_search_version = @WebSearchVersion,
|
||||
updated_at = @UpdatedAt
|
||||
WHERE agent_id = @AgentId AND user_id = @UserId
|
||||
", agent);
|
||||
@@ -668,8 +679,8 @@ public class DatabaseService
|
||||
adminUserId = Guid.NewGuid().ToString("N");
|
||||
var now = DateTime.UtcNow.ToString("o");
|
||||
conn.Execute(@"
|
||||
INSERT INTO users (user_id, username, password_hash, display_name, created_at, updated_at)
|
||||
VALUES (@userId, @username, @hash, @displayName, @now, @now)",
|
||||
INSERT INTO users (user_id, username, password_hash, display_name, is_admin, created_at, updated_at)
|
||||
VALUES (@userId, @username, @hash, @displayName, 1, @now, @now)",
|
||||
new
|
||||
{
|
||||
userId = adminUserId,
|
||||
@@ -687,6 +698,9 @@ public class DatabaseService
|
||||
// Bestehende Sessions/Agents ohne User dem ersten User zuweisen
|
||||
conn.Execute("UPDATE sessions SET user_id = @userId WHERE user_id IS NULL", new { userId = adminUserId });
|
||||
conn.Execute("UPDATE agents SET user_id = @userId WHERE user_id IS NULL", new { userId = adminUserId });
|
||||
|
||||
// Erster User ist immer Admin
|
||||
conn.Execute("UPDATE users SET is_admin = 1 WHERE user_id = @userId AND is_admin = 0", new { userId = adminUserId });
|
||||
}
|
||||
|
||||
public async Task<UserRecord> GetUserByUsername(string username)
|
||||
|
||||
Reference in new issue
Block a user