Refactor: Server-Settings von appsettings.json in DB-Tabelle (server_settings)

- Neuer ServerSettingsService: zentrale Settings in SQLite mit In-Memory-Cache
- Auto-Migration: bestehende appsettings.Production.json wird einmalig in DB übernommen
- Alle Services umgestellt: IConfiguration → ServerSettingsService (dynamisch, kein Neustart)
- Port bleibt als einziges Setting in appsettings.Production.json (erfordert Neustart)
- ServerSettingsEndpoint: liest/schreibt DB statt JSON-Datei, Port als Sonder-Setting
- IConfiguration komplett entfernt aus allen Services (ChatService, ToolService, AnthropicService,
  OpenAiService, KreaService, TelegramService, KeyScannerService, GitHubEventScannerService)
- AuthMiddleware: KreaApiKey dynamisch aus DB
- appsettings.json bereinigt: nur noch Port und Logging
- Dokumentation aktualisiert (ARCHITECTURE.md, ARCHITECTURE_QUICK.md, README.md)
This commit is contained in:
Meleki committed 2026-03-14 13:06:44 +01:00
1 parent 542a031172
commit 122000487c
18 files changed
+389 -270

No files matched your search

+5 -7
View File
@@ -19,21 +19,19 @@ public class AnthropicService
private readonly ToolService _toolService;
private readonly DatabaseService _db;
private readonly MemoryService _memoryService;
private readonly bool _payloadLogging;
private readonly bool _payloadLoggingGzip;
private readonly ServerSettingsService _settings;
private readonly VectorSearchService _vectorSearch;
private readonly EmbeddingBackgroundService _embeddingBg;
private ChatService _chatService;
private TelegramService _telegramService;
public AnthropicService(IHttpClientFactory httpFactory, ToolService toolService, DatabaseService db, MemoryService memoryService = null, bool payloadLogging = false, bool payloadLoggingGzip = false, VectorSearchService vectorSearch = null, EmbeddingBackgroundService embeddingBg = null)
public AnthropicService(IHttpClientFactory httpFactory, ToolService toolService, DatabaseService db, MemoryService memoryService = null, ServerSettingsService settings = null, VectorSearchService vectorSearch = null, EmbeddingBackgroundService embeddingBg = null)
{
_httpFactory = httpFactory;
_toolService = toolService;
_db = db;
_memoryService = memoryService;
_payloadLogging = payloadLogging;
_payloadLoggingGzip = payloadLoggingGzip;
_settings = settings;
_vectorSearch = vectorSearch;
_embeddingBg = embeddingBg;
}
@@ -1201,7 +1199,7 @@ public class AnthropicService
private void LogPayload(string json, string sessionId, string chainId, int iteration)
{
if (!_payloadLogging) return;
if (_settings == null || !_settings.GetBool("PayloadLogging", false)) return;
try
{
var logsDir = Path.Combine(AppContext.BaseDirectory, "data", "logs");
@@ -1209,7 +1207,7 @@ public class AnthropicService
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var baseName = $"{timestamp}-{sessionId}-{chainId}-iter{iteration}-payload";
if (_payloadLoggingGzip)
if (_settings?.GetBool("PayloadLoggingGzip", false) ?? false)
{
var filePath = Path.Combine(logsDir, baseName + ".json.gz");
using var fs = File.Create(filePath);
+10 -9
View File
@@ -14,20 +14,21 @@ public class ChatService
private readonly ToolService _toolService;
private readonly MemoryService _memoryService;
private readonly SkillService _skillService;
private readonly int _maxHistory;
private readonly int _reminderInterval;
private readonly ServerSettingsService _settings;
public ChatService(DatabaseService db, ToolService toolService, MemoryService memoryService, SkillService skillService,
int maxHistory = 120, int reminderInterval = 30)
ServerSettingsService settings)
{
_db = db;
_toolService = toolService;
_memoryService = memoryService;
_skillService = skillService;
_maxHistory = maxHistory;
_reminderInterval = reminderInterval;
_settings = settings;
}
private int MaxHistory => _settings.GetInt("MaxHistory", 120);
private int ReminderInterval => _settings.GetInt("ReminderInterval", 30);
public static string ResolveModelId(string model)
{
// OpenAI-kompatible Provider werden unverändert durchgereicht (Format: "provider:model")
@@ -487,9 +488,9 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop?
// History auf maximal N Messages begrenzen (älteste entfernen)
// WICHTIG: Nicht mitten in einem tool_use/tool_result-Paar abschneiden!
if (history.Count > _maxHistory)
if (history.Count > MaxHistory)
{
int removeCount = history.Count - _maxHistory;
int removeCount = history.Count - MaxHistory;
// Nach vorne korrigieren bis wir an einer sicheren Stelle sind:
// Nicht abschneiden wenn die erste verbleibende Message ein tool_result ist
// oder die letzte entfernte Message ein assistant mit tool_use ist
@@ -550,7 +551,7 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop?
// Reminder einfügen: alle ~30 user/assistant-Paare ein Reminder-Paar einfügen
var reminder = SystemPromptBuilder.BuildReminder(promptBlocks);
if (!string.IsNullOrWhiteSpace(reminder) && _reminderInterval > 0 && history.Count > _reminderInterval)
if (!string.IsNullOrWhiteSpace(reminder) && ReminderInterval > 0 && history.Count > ReminderInterval)
{
var reminderUser = new Dictionary<string, object>
{
@@ -569,7 +570,7 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop?
for (int i = 0; i < history.Count; i++)
{
messageCount++;
if (messageCount > 0 && messageCount % _reminderInterval == 0)
if (messageCount > 0 && messageCount % ReminderInterval == 0)
{
// Position nach dem aktuellen Element – aber nur einfügen wenn es eine
// assistant-Message ist (damit user/assistant-Alternierung nicht bricht)
+14 -19
View File
@@ -8,7 +8,6 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Gott.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@@ -23,8 +22,10 @@ public class GitHubEventScannerService : BackgroundService
private readonly DatabaseService _db;
private readonly IHttpClientFactory _httpFactory;
private readonly ILogger<GitHubEventScannerService> _logger;
private readonly string _githubToken;
private readonly bool _enabled;
private readonly ServerSettingsService _settings;
private string _githubToken => _settings.Get("GitHubToken");
private bool _enabled => _settings.GetBool("GitHubEventScanner", false);
private const int PollIntervalMs = 30_000; // 30 Sekunden (häufiger pollen, weniger verpassen)
private const int MaxCommitsPerPoll = 30; // Max Commit-Diffs pro Poll-Zyklus
@@ -90,31 +91,18 @@ public class GitHubEventScannerService : BackgroundService
public GitHubEventScannerService(
DatabaseService db,
IHttpClientFactory httpFactory,
IConfiguration config,
ServerSettingsService settings,
ILogger<GitHubEventScannerService> logger)
{
_db = db;
_httpFactory = httpFactory;
_settings = settings;
_logger = logger;
_githubToken = config["GitHubToken"] ?? "";
_enabled = config.GetValue<bool>("GitHubEventScanner", false);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
if (!_enabled)
{
_logger.LogInformation("GitHubEventScannerService deaktiviert (GitHubEventScanner=false)");
return;
}
if (string.IsNullOrWhiteSpace(_githubToken))
{
_logger.LogWarning("GitHubEventScannerService deaktiviert (kein GitHubToken konfiguriert)");
return;
}
_logger.LogInformation("GitHubEventScannerService gestartet (Poll-Intervall: {Interval}s)", PollIntervalMs / 1000);
_logger.LogInformation("GitHubEventScannerService bereit (Enabled wird dynamisch aus Settings gelesen)");
// Kurze Verzögerung nach App-Start
await Task.Delay(5_000, stoppingToken);
@@ -123,6 +111,13 @@ public class GitHubEventScannerService : BackgroundService
while (!stoppingToken.IsCancellationRequested)
{
// Dynamisch prüfen ob aktiviert
if (!_enabled || string.IsNullOrWhiteSpace(_githubToken))
{
lock (_lock) _isRunning = false;
await Task.Delay(10_000, stoppingToken);
continue;
}
try
{
lock (_lock) _isRunning = true;
+8 -21
View File
@@ -9,7 +9,6 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Gott.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@@ -20,9 +19,11 @@ public class KeyScannerService : BackgroundService
private readonly DatabaseService _db;
private readonly IHttpClientFactory _httpFactory;
private readonly ILogger<KeyScannerService> _logger;
private readonly string _githubToken;
private readonly string _serperApiKey;
private readonly int _intervalHours;
private readonly ServerSettingsService _settings;
private string _githubToken => _settings.Get("GitHubToken");
private string _serperApiKey => _settings.Get("SerperApiKey");
private int _intervalHours => _settings.GetInt("ScanIntervalHours", 0);
// Status-Tracking
private readonly object _lock = new();
@@ -75,15 +76,13 @@ public class KeyScannerService : BackgroundService
public KeyScannerService(
DatabaseService db,
IHttpClientFactory httpFactory,
IConfiguration config,
ServerSettingsService settings,
ILogger<KeyScannerService> logger)
{
_db = db;
_httpFactory = httpFactory;
_settings = settings;
_logger = logger;
_githubToken = config["GitHubToken"] ?? "";
_serperApiKey = config["SerperApiKey"] ?? "";
_intervalHours = config.GetValue<int>("ScanIntervalHours", 0);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
@@ -163,7 +162,6 @@ public class KeyScannerService : BackgroundService
var modeLabel = modeLabels.ContainsKey(mode) ? modeLabels[mode] : "Full";
AddLog("scan", modeLabel + "-Scan gestartet");
var newKeysFound = 0;
var checkedCount = 0;
var totalFound = 0;
try
@@ -230,21 +228,10 @@ public class KeyScannerService : BackgroundService
}
AddLog("scan", newKeysFound + " neue Key(s) gespeichert");
// Phase 3: Alle unchecked Keys prüfen
var allScanned = (await _db.GetScannedKeys()).ToList();
var toCheck = allScanned.Where(k => k.Status == "unchecked").ToList();
foreach (var sk in toCheck)
{
ct.ThrowIfCancellationRequested();
await CheckAndUpdateKey(sk);
checkedCount++;
await Task.Delay(500, ct); // Rate-Limit schonen
}
lock (_lock)
{
_lastRun = DateTime.UtcNow.ToString("o");
_lastResult = $"{modeLabel}: {totalFound} gefunden, {newKeysFound} neu, {checkedCount} geprüft";
_lastResult = $"{modeLabel}: {totalFound} gefunden, {newKeysFound} neu";
}
AddLog("scan", "Scan abgeschlossen – " + _lastResult);
+5 -3
View File
@@ -14,9 +14,11 @@ namespace Gott.Services
public class KreaService
{
private readonly IHttpClientFactory _httpFactory;
private readonly string _token;
private readonly ServerSettingsService _settings;
private readonly string _imagesDir;
private string _token => _settings.Get("KreaToken");
private const string BASE_URL = "https://api.krea.ai";
public static readonly Dictionary<string, string> IMAGE_MODELS = new Dictionary<string, string>
@@ -47,10 +49,10 @@ namespace Gott.Services
public bool IsConfigured => !string.IsNullOrWhiteSpace(_token);
public KreaService(IHttpClientFactory httpFactory, string token, string dataDir)
public KreaService(IHttpClientFactory httpFactory, ServerSettingsService settings, string dataDir)
{
_httpFactory = httpFactory;
_token = token;
_settings = settings;
_imagesDir = Path.Combine(dataDir, "images");
Directory.CreateDirectory(_imagesDir);
}
+10 -3
View File
@@ -156,17 +156,20 @@ public class OpenAiService
private List<ProviderModel> _openaiModels = new List<ProviderModel>();
private readonly string _cacheFile;
public OpenAiService(IHttpClientFactory httpFactory, DatabaseService db, ToolService toolService, Microsoft.Extensions.Configuration.IConfiguration configuration, string dataDir)
private readonly ServerSettingsService _settings;
public OpenAiService(IHttpClientFactory httpFactory, DatabaseService db, ToolService toolService, ServerSettingsService settings, string dataDir)
{
_httpFactory = httpFactory;
_db = db;
_toolService = toolService;
_settings = settings;
_cacheFile = Path.Combine(dataDir, "models_cache.json");
Providers = new Dictionary<string, (string BaseUrl, string ApiKey)>
{
["nvidia"] = ("https://g4f.space/api/nvidia/v1", ""),
["pollinations"] = ("https://text.pollinations.ai/openai", ""),
["gemini"] = ("https://generativelanguage.googleapis.com/v1beta/openai", configuration["GeminiApiKey"] ?? ""),
["gemini"] = ("https://generativelanguage.googleapis.com/v1beta/openai", ""), // Key kommt dynamisch aus Settings
["openai"] = ("https://api.openai.com/v1", "") // Key kommt dynamisch aus DB
};
}
@@ -209,7 +212,7 @@ public class OpenAiService
_nvidiaModels = await FetchModels("https://g4f.space/api/nvidia/models", "Nvidia");
_pollinationsModels = await FetchModels("https://text.pollinations.ai/models", "Pollinations");
var geminiApiKey = Providers["gemini"].ApiKey;
var geminiApiKey = _settings.Get("GeminiApiKey");
if (!string.IsNullOrEmpty(geminiApiKey))
_geminiModels = await FetchModels("https://generativelanguage.googleapis.com/v1beta/openai/models", "Gemini", geminiApiKey);
else
@@ -363,6 +366,10 @@ public class OpenAiService
if (!string.IsNullOrEmpty(apiKeyOverride))
providerConfig = (providerConfig.BaseUrl, apiKeyOverride);
// Gemini: Key dynamisch aus ServerSettings holen (falls nicht per Override gesetzt)
if (provider == "gemini" && string.IsNullOrEmpty(providerConfig.ApiKey))
providerConfig = (providerConfig.BaseUrl, _settings.Get("GeminiApiKey"));
try
{
// OpenAI-kompatibles Messages-Format aufbauen
+236
View File
@@ -0,0 +1,236 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.Data.Sqlite;
namespace Gott.Services;
/// <summary>
/// Zentraler Settings-Service. Liest/schreibt aus der DB-Tabelle 'server_settings'.
/// In-Memory-Cache für schnellen Zugriff. Kein Neustart bei Änderungen nötig.
/// </summary>
public class ServerSettingsService
{
private readonly string _dbPath;
private readonly Dictionary<string, string> _cache = new Dictionary<string, string>();
private readonly object _lock = new object();
// Definiert alle Settings mit Typ, Default und Beschreibung
public static readonly List<SettingDefinition> Definitions = new List<SettingDefinition>
{
new SettingDefinition( "ProxyUrl", "string", "", "HTTP/SOCKS Proxy (erfordert Neustart)", "text" ),
new SettingDefinition( "MaxIterations", "int", "100", "Max Tool-Loop Iterationen pro Chat", "number" ),
new SettingDefinition( "MaxHistory", "int", "120", "Max History-Messages pro Request", "number" ),
new SettingDefinition( "ReminderInterval", "int", "30", "System-Prompt Reminder alle N Messages (0 = deaktiviert)", "number" ),
new SettingDefinition( "ExecuteTimeout", "int", "180", "Timeout für Shell-Befehle in Sekunden", "number" ),
new SettingDefinition( "PayloadLogging", "bool", "false", "API-Payloads loggen (Debug)", "toggle" ),
new SettingDefinition( "PayloadLoggingGzip", "bool", "false", "Payload-Logs als .gz komprimieren", "toggle" ),
new SettingDefinition( "KreaToken", "string", "", "Krea API Token", "password" ),
new SettingDefinition( "KreaApiKey", "string", "", "API-Key für Krea-Endpoints (Automation)", "password" ),
new SettingDefinition( "GeminiApiKey", "string", "", "Gemini API-Key (für Gemini-Provider im Chat)", "password" ),
new SettingDefinition( "GitHubToken", "string", "", "GitHub PAT für Key-Scanner und Code-Search Tool", "password" ),
new SettingDefinition( "SerperApiKey", "string", "", "Serper.dev API-Key für Google-Suche (Key-Scanner + Tool)", "password" ),
new SettingDefinition( "ScanIntervalHours", "int", "0", "Key-Scanner Intervall in Stunden (0 = nur manuell)", "number" ),
new SettingDefinition( "GitHubEventScanner", "bool", "false", "GitHub Event Scanner (Echtzeit-Monitoring öffentlicher Pushes)", "toggle" ),
};
public ServerSettingsService(string dbPath)
{
_dbPath = dbPath;
EnsureTable();
LoadCache();
}
private void EnsureTable()
{
using var conn = new SqliteConnection($"Data Source={_dbPath}");
conn.Open();
using var cmd = conn.CreateCommand();
cmd.CommandText = @"
CREATE TABLE IF NOT EXISTS server_settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL DEFAULT '',
type TEXT NOT NULL DEFAULT 'string'
)";
cmd.ExecuteNonQuery();
}
private void LoadCache()
{
lock (_lock)
{
_cache.Clear();
using var conn = new SqliteConnection($"Data Source={_dbPath}");
conn.Open();
using var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT key, value FROM server_settings";
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
_cache[reader.GetString(0)] = reader.GetString(1);
}
}
}
/// <summary>
/// Migriert bestehende Werte aus appsettings.Production.json in die DB (einmalig).
/// Nur Settings die noch NICHT in der DB sind werden übernommen.
/// </summary>
public void MigrateFromAppSettings(string contentRootPath)
{
var prodPath = Path.Combine(contentRootPath, "appsettings.Production.json");
if (!File.Exists(prodPath)) return;
try
{
var json = File.ReadAllText(prodPath);
var obj = JsonNode.Parse(json) as JsonObject;
if (obj == null) return;
// Port bleibt in appsettings – nicht migrieren
var skipKeys = new HashSet<string> { "Port", "Logging", "AllowedHosts" };
foreach (var def in Definitions)
{
if (skipKeys.Contains(def.Key)) continue;
if (!obj.ContainsKey(def.Key)) continue;
// Nur migrieren wenn nicht schon in DB
lock (_lock)
{
if (_cache.ContainsKey(def.Key)) continue;
}
var node = obj[def.Key];
string value;
switch (def.Type)
{
case "bool":
value = (node?.GetValue<bool>() ?? false) ? "true" : "false";
break;
case "int":
value = (node?.GetValue<int>() ?? 0).ToString();
break;
default:
value = node?.GetValue<string>() ?? "";
break;
}
// Default-Werte nicht migrieren (kein Müll in DB)
if (value == def.DefaultValue) continue;
Set(def.Key, value);
}
}
catch
{
// Fehler bei Migration ignorieren – Settings sind halt Default
}
}
// ─── Getter ────────────────────────────────────────────────
public string Get(string key, string defaultValue = "")
{
lock (_lock)
{
return _cache.TryGetValue(key, out var val) ? val : defaultValue;
}
}
public int GetInt(string key, int defaultValue = 0)
{
var raw = Get(key, null);
return raw != null && int.TryParse(raw, out var v) ? v : defaultValue;
}
public bool GetBool(string key, bool defaultValue = false)
{
var raw = Get(key, null);
if (raw == null) return defaultValue;
return raw.Equals("true", StringComparison.OrdinalIgnoreCase) || raw == "1";
}
// ─── Setter ────────────────────────────────────────────────
public void Set(string key, string value)
{
// Typ aus Definitions holen
var def = Definitions.Find(d => d.Key == key);
var type = def?.Type ?? "string";
using var conn = new SqliteConnection($"Data Source={_dbPath}");
conn.Open();
using var cmd = conn.CreateCommand();
cmd.CommandText = @"
INSERT INTO server_settings (key, value, type) VALUES (@key, @value, @type)
ON CONFLICT(key) DO UPDATE SET value = @value, type = @type";
cmd.Parameters.AddWithValue("@key", key);
cmd.Parameters.AddWithValue("@value", value ?? "");
cmd.Parameters.AddWithValue("@type", type);
cmd.ExecuteNonQuery();
lock (_lock)
{
_cache[key] = value ?? "";
}
}
public void SetInt(string key, int value) => Set(key, value.ToString());
public void SetBool(string key, bool value) => Set(key, value ? "true" : "false");
// ─── Bulk-Operationen (für Endpoint) ───────────────────────
public List<object> GetAll()
{
var result = new List<object>();
foreach (var def in Definitions)
{
object value;
switch (def.Type)
{
case "int":
value = GetInt(def.Key, int.TryParse(def.DefaultValue, out var dv) ? dv : 0);
break;
case "bool":
value = GetBool(def.Key, def.DefaultValue.Equals("true", StringComparison.OrdinalIgnoreCase));
break;
default:
value = Get(def.Key, def.DefaultValue);
break;
}
result.Add(new
{
key = def.Key,
type = def.Type,
value = value,
label = def.Label,
inputType = def.InputType,
});
}
return result;
}
// ─── Definition ────────────────────────────────────────────
public class SettingDefinition
{
public string Key { get; }
public string Type { get; }
public string DefaultValue { get; }
public string Label { get; }
public string InputType { get; }
public SettingDefinition(string key, string type, string defaultValue, string label, string inputType)
{
Key = key;
Type = type;
DefaultValue = defaultValue;
Label = label;
InputType = inputType;
}
}
}
+4 -4
View File
@@ -15,20 +15,20 @@ public class TelegramService
private readonly DatabaseService _db;
private readonly ChatService _chatService;
private readonly AnthropicService _anthropic;
private readonly int _maxIterations;
private readonly ServerSettingsService _settings;
public TelegramService(
IHttpClientFactory httpFactory,
DatabaseService db,
ChatService chatService,
AnthropicService anthropic,
int maxIterations = 100)
ServerSettingsService settings)
{
_httpFactory = httpFactory;
_db = db;
_chatService = chatService;
_anthropic = anthropic;
_maxIterations = maxIterations;
_settings = settings;
}
// ── Webhook-Update verarbeiten ──
@@ -161,7 +161,7 @@ public class TelegramService
private async Task HandleMessage(long chatId, string userText, string botToken, string botUserId, SessionRecord session)
{
var ctx = await _chatService.PrepareContext(session.SessionId, botUserId, _maxIterations);
var ctx = await _chatService.PrepareContext(session.SessionId, botUserId, _settings.GetInt("MaxIterations", 100));
if (ctx == null)
{
await SendMessage(chatId, botToken, "❌ Kein Agent konfiguriert. Bitte weise der Session im Web-UI einen Agent zu.");
+8 -9
View File
@@ -10,7 +10,6 @@ using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.FileSystemGlobbing.Abstractions;
@@ -18,12 +17,12 @@ namespace Gott.Services;
public class ToolService
{
private readonly IConfiguration _config;
private readonly ServerSettingsService _settings;
private IHttpClientFactory _httpFactory;
public ToolService(IConfiguration config)
public ToolService(ServerSettingsService settings)
{
_config = config;
_settings = settings;
}
public void SetHttpClientFactory(IHttpClientFactory httpFactory)
@@ -31,9 +30,9 @@ public class ToolService
_httpFactory = httpFactory;
}
private int ExecuteTimeoutSeconds => _config.GetValue<int>("ExecuteTimeout", 180);
public bool HasGitHubToken => !string.IsNullOrWhiteSpace(_config["GitHubToken"]);
public bool HasSerperKey => !string.IsNullOrWhiteSpace(_config["SerperApiKey"]);
private int ExecuteTimeoutSeconds => _settings.GetInt("ExecuteTimeout", 180);
public bool HasGitHubToken => !string.IsNullOrWhiteSpace(_settings.Get("GitHubToken"));
public bool HasSerperKey => !string.IsNullOrWhiteSpace(_settings.Get("SerperApiKey"));
public const string AskUserChoiceToolName = "ask_user_choice";
/// <summary>
@@ -858,7 +857,7 @@ public class ToolService
private async Task<string> ExecuteGitHubCodeSearch(JsonElement input, CancellationToken ct)
{
var token = _config["GitHubToken"] ?? "";
var token = _settings.Get("GitHubToken");
if (string.IsNullOrWhiteSpace(token))
return "Fehler: Kein GitHubToken konfiguriert. Bitte in den Server-Einstellungen hinterlegen.";
@@ -961,7 +960,7 @@ public class ToolService
private async Task<string> ExecuteSerperWebSearch(JsonElement input, CancellationToken ct)
{
var apiKey = _config["SerperApiKey"] ?? "";
var apiKey = _settings.Get("SerperApiKey");
if (string.IsNullOrWhiteSpace(apiKey))
return "Fehler: Kein SerperApiKey konfiguriert. Bitte in den Server-Einstellungen hinterlegen.";