User-spezifische API-Key-Auswahl, globales is_active entfernt
Backend:
- is_active komplett entfernt, durch is_public (Admin-Toggle) ersetzt
- Neues selected_api_key_id auf users-Tabelle
- AnthropicService: globaler _token entfernt, apiToken pro Request
- Token-Durchreichung: StreamChat → ProcessStream → RunAgentChat →
RunPreparer/RunReview/RunFinalizer, ExecuteTool, send_to_session
- ChatEndpoint: User-Key aus DB laden, an StreamChat übergeben
- TelegramService: Key per bot.UserId auflösen
- CronjobService: Key per job.UserId auflösen
- Neue Endpoints: GET /api/api-keys/available, PUT /api/api-keys/select/{keyId},
GET /api/api-keys/selected, PUT /api/admin/api-keys/{keyId}/public,
POST /api/admin/api-keys/check-all
- Program.cs: AnthropicService ohne Token-Parameter
Frontend:
- API-Keys-Page: Public-Toggle (Globe/Lock) statt Activate/Deactivate
- Alle prüfen Button für Batch-Key-Check
- Chat: Key-Auswahl für alle User (nicht nur Admin)
- Dummy-Badge wenn kein Key ausgewählt
- Fallback-Badge bei Key ohne Usage-Daten
- Key-Switch-Modal: apiGetAvailableKeys + apiSelectApiKey
This commit is contained in:
1 parent
cc46cf2bbc
commit
dde0fede7d
18 files changed
+340
-193
No files matched your search
@@ -16,7 +16,6 @@ namespace Gott.Services;
|
||||
public class AnthropicService
|
||||
{
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private string _token;
|
||||
private readonly ToolService _toolService;
|
||||
private readonly DatabaseService _db;
|
||||
private readonly MemoryService _memoryService;
|
||||
@@ -27,10 +26,9 @@ public class AnthropicService
|
||||
private ChatService _chatService;
|
||||
private TelegramService _telegramService;
|
||||
|
||||
public AnthropicService(IHttpClientFactory httpFactory, string token, 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, bool payloadLogging = false, bool payloadLoggingGzip = false, VectorSearchService vectorSearch = null, EmbeddingBackgroundService embeddingBg = null)
|
||||
{
|
||||
_httpFactory = httpFactory;
|
||||
_token = token;
|
||||
_toolService = toolService;
|
||||
_db = db;
|
||||
_memoryService = memoryService;
|
||||
@@ -54,25 +52,17 @@ public class AnthropicService
|
||||
_telegramService = telegramService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aktualisiert den API-Token zur Laufzeit (z.B. wenn ein anderer Key aktiviert wird).
|
||||
/// </summary>
|
||||
public void UpdateToken(string token)
|
||||
{
|
||||
_token = token ?? "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liest Usage-Header aus einer Anthropic-Response und aktualisiert die api_keys-Tabelle.
|
||||
/// Fire-and-forget – Fehler werden stillschweigend ignoriert.
|
||||
/// </summary>
|
||||
private void TryUpdateKeyMeta(HttpResponseMessage httpResp)
|
||||
private void TryUpdateKeyMeta(HttpResponseMessage httpResp, string apiToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var meta = AnthropicKeyChecker.ExtractMetaFromHeaders(httpResp.Headers);
|
||||
if (meta == null || string.IsNullOrEmpty(_token)) return;
|
||||
_ = _db.UpdateApiKeyMetaByToken(_token, meta.Status, meta.Plan, meta.Usage7d, meta.Usage5h);
|
||||
if (meta == null || string.IsNullOrEmpty(apiToken)) return;
|
||||
_ = _db.UpdateApiKeyMetaByToken(apiToken, meta.Status, meta.Plan, meta.Usage7d, meta.Usage5h);
|
||||
}
|
||||
catch { /* silent */ }
|
||||
}
|
||||
@@ -84,6 +74,7 @@ public class AnthropicService
|
||||
List<object> messages,
|
||||
string chainId,
|
||||
string workspace,
|
||||
string apiToken,
|
||||
string sessionId = null,
|
||||
string agentId = null,
|
||||
string userId = null,
|
||||
@@ -181,7 +172,7 @@ public class AnthropicService
|
||||
var hasRemote = remoteMgr?.GetBySessionId(sessionId) != null;
|
||||
AddToolsToPayload(payload, workspace, agentId, useMemory, webSearchVersion, toolChoice, iterations, allowUserChoice, planMode, includeRemote: hasRemote, includeGitHubSearch: useGitHubSearch && _toolService.HasGitHubToken, includeSerperSearch: useSerperSearch && _toolService.HasSerperKey);
|
||||
|
||||
var client = CreateClient();
|
||||
var client = CreateClient(apiToken);
|
||||
var json = JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true });
|
||||
LogPayload(json, sessionId, chainId, iterations);
|
||||
var httpContent = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
@@ -192,7 +183,7 @@ public class AnthropicService
|
||||
workCt
|
||||
);
|
||||
|
||||
TryUpdateKeyMeta(httpResp);
|
||||
TryUpdateKeyMeta(httpResp, apiToken);
|
||||
|
||||
if (!httpResp.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -227,7 +218,7 @@ public class AnthropicService
|
||||
return;
|
||||
}
|
||||
|
||||
var (stopReason, assistantContent, executedTools, streamErrorInfo) = await ProcessStream(response, httpResp, usage, workspace, agentId, workCt, sseCt, remoteMgr, sessionId, userId);
|
||||
var (stopReason, assistantContent, executedTools, streamErrorInfo) = await ProcessStream(response, httpResp, usage, workspace, agentId, workCt, sseCt, remoteMgr, sessionId, userId, apiToken);
|
||||
lastStopReason = stopReason;
|
||||
promptTrimRetries = 0; // Erfolgreicher API-Call → Retry-Counter resetten
|
||||
|
||||
@@ -311,7 +302,7 @@ public class AnthropicService
|
||||
// ── Review-Agent Phase ──
|
||||
if (review != null && stopReason == "end_turn" && reviewRound < review.MaxReviewIterations)
|
||||
{
|
||||
var reviewResult = await RunReview(response, review, messages, chainId, workspace, sessionId, usage, iterations, reviewRound, workCt, sseCt);
|
||||
var reviewResult = await RunReview(response, review, messages, chainId, workspace, sessionId, apiToken, usage, iterations, reviewRound, workCt, sseCt);
|
||||
iterations += reviewResult.ReviewIterationCount;
|
||||
reviewRound++;
|
||||
|
||||
@@ -336,7 +327,7 @@ public class AnthropicService
|
||||
// ── Finalizer-Agent Phase ──
|
||||
if (finalizer != null && stopReason == "end_turn")
|
||||
{
|
||||
var finalizerIterations = await RunFinalizer(response, finalizer, messages, chainId, workspace, sessionId, usage, iterations, workCt, sseCt);
|
||||
var finalizerIterations = await RunFinalizer(response, finalizer, messages, chainId, workspace, sessionId, apiToken, usage, iterations, workCt, sseCt);
|
||||
iterations += finalizerIterations;
|
||||
}
|
||||
|
||||
@@ -467,6 +458,7 @@ public class AnthropicService
|
||||
string chainId,
|
||||
string workspace,
|
||||
string sessionId,
|
||||
string apiToken,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Preparer läuft VOR dem Worker und nutzt den Request-CancellationToken direkt.
|
||||
@@ -487,7 +479,7 @@ public class AnthropicService
|
||||
var (text, iterations) = await RunAgentChat(
|
||||
response, preparer.ModelId, preparer.SystemPrompt, preparer.AgentId,
|
||||
preparer.Workspace, preparer.UseMemory, preparer.WebSearchVersion,
|
||||
preparer.ToolChoice, preparerMessages, chainId, sessionId, usage,
|
||||
preparer.ToolChoice, preparerMessages, chainId, sessionId, apiToken, usage,
|
||||
0, "preparer", preparer.Agent?.Name ?? "Vorbereiter-Agent",
|
||||
cancellationToken, cancellationToken);
|
||||
|
||||
@@ -555,6 +547,7 @@ public class AnthropicService
|
||||
string chainId,
|
||||
string workspace,
|
||||
string sessionId,
|
||||
string apiToken,
|
||||
TokenUsage usage,
|
||||
int baseIterations,
|
||||
int reviewRound,
|
||||
@@ -579,7 +572,7 @@ public class AnthropicService
|
||||
var (reviewText, reviewIterations) = await RunAgentChat(
|
||||
response, review.ModelId, review.SystemPrompt, review.AgentId,
|
||||
workspace, review.UseMemory, review.WebSearchVersion,
|
||||
review.ToolChoice, reviewMessages, chainId, sessionId, usage,
|
||||
review.ToolChoice, reviewMessages, chainId, sessionId, apiToken, usage,
|
||||
baseIterations, "review", review.Agent?.Name ?? "Review-Agent",
|
||||
workCt, sseCt);
|
||||
|
||||
@@ -630,6 +623,7 @@ public class AnthropicService
|
||||
string chainId,
|
||||
string workspace,
|
||||
string sessionId,
|
||||
string apiToken,
|
||||
TokenUsage usage,
|
||||
int baseIterations,
|
||||
CancellationToken workCt,
|
||||
@@ -647,7 +641,7 @@ public class AnthropicService
|
||||
var (text, iterations) = await RunAgentChat(
|
||||
response, finalizer.ModelId, finalizer.SystemPrompt, finalizer.AgentId,
|
||||
finalizer.Workspace, finalizer.UseMemory, finalizer.WebSearchVersion,
|
||||
finalizer.ToolChoice, finalizerMessages, chainId, sessionId, usage,
|
||||
finalizer.ToolChoice, finalizerMessages, chainId, sessionId, apiToken, usage,
|
||||
baseIterations, "finalizer", finalizer.Agent?.Name ?? "Finalizer-Agent",
|
||||
workCt, sseCt);
|
||||
|
||||
@@ -794,6 +788,7 @@ public class AnthropicService
|
||||
List<object> messages,
|
||||
string chainId,
|
||||
string sessionId,
|
||||
string apiToken,
|
||||
TokenUsage usage,
|
||||
int baseIterations,
|
||||
string agentType,
|
||||
@@ -825,7 +820,7 @@ public class AnthropicService
|
||||
|
||||
AddToolsToPayload(payload, agentWorkspace, agentId, useMemory, webSearchVersion, agentToolChoice, iterations);
|
||||
|
||||
var client = CreateClient();
|
||||
var client = CreateClient(apiToken);
|
||||
var json = JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true });
|
||||
LogPayload(json, sessionId, chainId, baseIterations + iterations);
|
||||
var httpContent = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
@@ -834,7 +829,7 @@ public class AnthropicService
|
||||
new HttpRequestMessage(HttpMethod.Post, "https://api.anthropic.com/v1/messages") { Content = httpContent },
|
||||
cancellationToken: workCt);
|
||||
|
||||
TryUpdateKeyMeta(httpResp);
|
||||
TryUpdateKeyMeta(httpResp, apiToken);
|
||||
|
||||
var respBody = await httpResp.Content.ReadAsStringAsync(workCt);
|
||||
|
||||
@@ -925,7 +920,7 @@ public class AnthropicService
|
||||
|
||||
string toolResult;
|
||||
bool isError = false;
|
||||
try { toolResult = await _toolService.ExecuteTool(toolName, toolInput, agentWorkspace, _memoryService, agentId, workCt, vectorSearch: _vectorSearch, embeddingBg: _embeddingBg, db: _db, chatService: _chatService, anthropicService: this, telegramService: _telegramService); }
|
||||
try { toolResult = await _toolService.ExecuteTool(toolName, toolInput, agentWorkspace, _memoryService, agentId, workCt, vectorSearch: _vectorSearch, embeddingBg: _embeddingBg, db: _db, chatService: _chatService, anthropicService: this, telegramService: _telegramService, apiToken: apiToken); }
|
||||
catch (OperationCanceledException) { throw; }
|
||||
catch (Exception ex) { toolResult = $"Fehler: {ex.Message}"; isError = true; }
|
||||
|
||||
@@ -1047,7 +1042,7 @@ public class AnthropicService
|
||||
|
||||
AddToolsToPayload(payload, ctx.Workspace, ctx.AgentId, ctx.UseMemory, ctx.WebSearchVersion, ctx.ToolChoice, iterations);
|
||||
|
||||
var client = CreateClient();
|
||||
var client = CreateClient(ctx.ApiToken);
|
||||
var json = JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true });
|
||||
LogPayload(json, ctx.SessionId, ctx.ChainId, iterations);
|
||||
var httpContent = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
@@ -1055,7 +1050,7 @@ public class AnthropicService
|
||||
using var httpResp = await client.SendAsync(
|
||||
new HttpRequestMessage(HttpMethod.Post, "https://api.anthropic.com/v1/messages") { Content = httpContent });
|
||||
|
||||
TryUpdateKeyMeta(httpResp);
|
||||
TryUpdateKeyMeta(httpResp, ctx.ApiToken);
|
||||
|
||||
var respBody = await httpResp.Content.ReadAsStringAsync();
|
||||
|
||||
@@ -1133,7 +1128,7 @@ public class AnthropicService
|
||||
|
||||
string toolResult;
|
||||
bool isError = false;
|
||||
try { toolResult = await _toolService.ExecuteTool(toolName, toolInput, ctx.Workspace, _memoryService, ctx.AgentId, vectorSearch: _vectorSearch, embeddingBg: _embeddingBg, db: _db, chatService: _chatService, anthropicService: this, userId: ctx.Session?.UserId, telegramService: _telegramService); }
|
||||
try { toolResult = await _toolService.ExecuteTool(toolName, toolInput, ctx.Workspace, _memoryService, ctx.AgentId, vectorSearch: _vectorSearch, embeddingBg: _embeddingBg, db: _db, chatService: _chatService, anthropicService: this, userId: ctx.Session?.UserId, telegramService: _telegramService, apiToken: ctx.ApiToken); }
|
||||
catch (OperationCanceledException) { throw; }
|
||||
catch (Exception ex) { toolResult = $"Fehler: {ex.Message}"; isError = true; }
|
||||
|
||||
@@ -1243,7 +1238,8 @@ public class AnthropicService
|
||||
CancellationToken sseCt = default,
|
||||
RemoteConnectionManager remoteMgr = null,
|
||||
string sessionId = null,
|
||||
string userId = null)
|
||||
string userId = null,
|
||||
string apiToken = null)
|
||||
{
|
||||
var stream = await httpResp.Content.ReadAsStreamAsync(workCt);
|
||||
// Bei Cancellation den Stream disposen, damit ReadLineAsync sofort abbricht.
|
||||
@@ -1425,7 +1421,7 @@ public class AnthropicService
|
||||
|
||||
string toolResult;
|
||||
bool isError = false;
|
||||
try { toolResult = await _toolService.ExecuteTool(currentToolName, inputEl, workspace, _memoryService, agentId, workCt, remoteMgr: remoteMgr, sessionId: sessionId, vectorSearch: _vectorSearch, embeddingBg: _embeddingBg, db: _db, chatService: _chatService, anthropicService: this, userId: userId, telegramService: _telegramService); }
|
||||
try { toolResult = await _toolService.ExecuteTool(currentToolName, inputEl, workspace, _memoryService, agentId, workCt, remoteMgr: remoteMgr, sessionId: sessionId, vectorSearch: _vectorSearch, embeddingBg: _embeddingBg, db: _db, chatService: _chatService, anthropicService: this, userId: userId, telegramService: _telegramService, apiToken: apiToken); }
|
||||
catch (OperationCanceledException) { throw; }
|
||||
catch (Exception ex) { toolResult = $"Fehler: {ex.Message}"; isError = true; }
|
||||
|
||||
@@ -1694,10 +1690,10 @@ public class AnthropicService
|
||||
return 0;
|
||||
}
|
||||
|
||||
private HttpClient CreateClient()
|
||||
private HttpClient CreateClient(string apiToken)
|
||||
{
|
||||
var client = _httpFactory.CreateClient();
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token);
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiToken);
|
||||
client.DefaultRequestHeaders.Add("anthropic-version", "2023-06-01");
|
||||
client.DefaultRequestHeaders.Add("anthropic-beta", "claude-code-20250219,oauth-2025-04-20,code-execution-2025-08-25");
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "claude-cli/2.1.2 (external, cli)");
|
||||
|
||||
@@ -86,6 +86,12 @@ public class CronjobService : BackgroundService
|
||||
if (ctx == null)
|
||||
throw new Exception("Session oder Agent nicht gefunden / nicht konfiguriert");
|
||||
|
||||
// API-Key des Cronjob-Owners auflösen
|
||||
var userApiKey = await _db.GetUserSelectedApiKey(job.UserId);
|
||||
if (userApiKey == null)
|
||||
throw new Exception($"Kein API-Key für User {job.UserId} ausgewählt");
|
||||
ctx.ApiToken = userApiKey.ApiKey;
|
||||
|
||||
// History aus DB laden
|
||||
var history = await _chatService.BuildHistoryFromDb(job.SessionId, promptBlocks: ctx.PromptBlocks);
|
||||
|
||||
@@ -134,6 +140,15 @@ public class CronjobService : BackgroundService
|
||||
return;
|
||||
}
|
||||
|
||||
// API-Key des Cronjob-Owners auflösen
|
||||
var notifyApiKey = await _db.GetUserSelectedApiKey(job.UserId);
|
||||
if (notifyApiKey == null)
|
||||
{
|
||||
_logger.LogWarning("Kein API-Key für User {UserId} (Cronjob '{Name}' Notification)", job.UserId, job.Name);
|
||||
return;
|
||||
}
|
||||
notifyCtx.ApiToken = notifyApiKey.ApiKey;
|
||||
|
||||
// Kurze Zusammenfassung als User-Nachricht in die Notify-Session schreiben
|
||||
var summary = $"[Cronjob \"{job.Name}\" wurde in Session \"{job.SessionName}\" ausgeführt]\n\nAntwort:\n{Truncate(responseText, 2000)}";
|
||||
|
||||
|
||||
@@ -324,7 +324,7 @@ public partial class DatabaseService
|
||||
{
|
||||
var now = DateTime.UtcNow.ToString("o");
|
||||
conn.Execute(@"
|
||||
INSERT INTO api_keys (key_id, provider, name, api_key, is_active, created_at, updated_at)
|
||||
INSERT INTO api_keys (key_id, provider, name, api_key, is_public, created_at, updated_at)
|
||||
VALUES (@keyId, 'anthropic', 'Migriert aus Config', @token, 1, @now, @now)",
|
||||
new { keyId = Guid.NewGuid().ToString("N"), token, now });
|
||||
Console.WriteLine("[Migration] AnthropicToken aus appsettings.Production.json → api_keys migriert");
|
||||
@@ -351,12 +351,11 @@ public partial class DatabaseService
|
||||
new { provider });
|
||||
}
|
||||
|
||||
public async Task<ApiKeyRecord> GetActiveApiKey(string provider)
|
||||
public async Task<IEnumerable<ApiKeyRecord>> GetPublicApiKeys()
|
||||
{
|
||||
using var conn = CreateConnection();
|
||||
return await conn.QuerySingleOrDefaultAsync<ApiKeyRecord>(
|
||||
"SELECT * FROM api_keys WHERE provider = @provider AND is_active = 1 LIMIT 1",
|
||||
new { provider });
|
||||
return await conn.QueryAsync<ApiKeyRecord>(
|
||||
"SELECT * FROM api_keys WHERE is_public = 1 ORDER BY provider, name");
|
||||
}
|
||||
|
||||
public async Task<ApiKeyRecord> GetApiKey(string keyId)
|
||||
@@ -372,8 +371,8 @@ public partial class DatabaseService
|
||||
{
|
||||
using var conn = CreateConnection();
|
||||
await conn.ExecuteAsync(@"
|
||||
INSERT INTO api_keys (key_id, provider, name, api_key, is_active, status, plan, usage_7d, usage_5h, checked_at, source_url, created_at, updated_at)
|
||||
VALUES (@KeyId, @Provider, @Name, @ApiKey, @IsActive, @Status, @Plan, @Usage7d, @Usage5h, @CheckedAt, @SourceUrl, @CreatedAt, @UpdatedAt)",
|
||||
INSERT INTO api_keys (key_id, provider, name, api_key, is_public, status, plan, usage_7d, usage_5h, checked_at, source_url, created_at, updated_at)
|
||||
VALUES (@KeyId, @Provider, @Name, @ApiKey, @IsPublic, @Status, @Plan, @Usage7d, @Usage5h, @CheckedAt, @SourceUrl, @CreatedAt, @UpdatedAt)",
|
||||
record);
|
||||
return await conn.ExecuteScalarAsync<long>("SELECT last_insert_rowid()");
|
||||
}
|
||||
@@ -409,29 +408,16 @@ public partial class DatabaseService
|
||||
using var conn = CreateConnection();
|
||||
await conn.ExecuteAsync(@"
|
||||
UPDATE api_keys SET status = @status, plan = @plan, usage_7d = @usage7d, usage_5h = @usage5h, checked_at = @now, updated_at = @now
|
||||
WHERE api_key = @apiKey AND is_active = 1",
|
||||
WHERE api_key = @apiKey",
|
||||
new { apiKey, status, plan, usage7d, usage5h, now = DateTime.UtcNow.ToString("o") });
|
||||
}
|
||||
|
||||
public async Task SetActiveApiKey(string keyId, string provider)
|
||||
{
|
||||
using var conn = CreateConnection();
|
||||
// Erst alle deaktivieren
|
||||
await conn.ExecuteAsync(
|
||||
"UPDATE api_keys SET is_active = 0, updated_at = @now WHERE provider = @provider",
|
||||
new { provider, now = DateTime.UtcNow.ToString("o") });
|
||||
// Dann den gewählten aktivieren
|
||||
await conn.ExecuteAsync(
|
||||
"UPDATE api_keys SET is_active = 1, updated_at = @now WHERE key_id = @keyId",
|
||||
new { keyId, now = DateTime.UtcNow.ToString("o") });
|
||||
}
|
||||
|
||||
public async Task DeactivateApiKey(string keyId)
|
||||
public async Task SetApiKeyPublic(string keyId, bool isPublic)
|
||||
{
|
||||
using var conn = CreateConnection();
|
||||
await conn.ExecuteAsync(
|
||||
"UPDATE api_keys SET is_active = 0, updated_at = @now WHERE key_id = @keyId",
|
||||
new { keyId, now = DateTime.UtcNow.ToString("o") });
|
||||
"UPDATE api_keys SET is_public = @isPublic, updated_at = @now WHERE key_id = @keyId",
|
||||
new { keyId, isPublic, now = DateTime.UtcNow.ToString("o") });
|
||||
}
|
||||
|
||||
public async Task DeleteApiKey(string keyId)
|
||||
|
||||
@@ -187,4 +187,32 @@ public partial class DatabaseService
|
||||
await conn.ExecuteAsync("DELETE FROM users WHERE user_id = @userId", new { userId });
|
||||
}
|
||||
|
||||
// ── User API-Key Auswahl ──
|
||||
|
||||
public async Task SetUserSelectedApiKey(string userId, string keyId)
|
||||
{
|
||||
using var conn = CreateConnection();
|
||||
await conn.ExecuteAsync(
|
||||
"UPDATE users SET selected_api_key_id = @keyId, updated_at = @now WHERE user_id = @userId",
|
||||
new { userId, keyId = (object)keyId ?? DBNull.Value, now = DateTime.UtcNow.ToString("o") });
|
||||
}
|
||||
|
||||
public async Task<string> GetUserSelectedApiKeyId(string userId)
|
||||
{
|
||||
using var conn = CreateConnection();
|
||||
return await conn.QuerySingleOrDefaultAsync<string>(
|
||||
"SELECT selected_api_key_id FROM users WHERE user_id = @userId",
|
||||
new { userId });
|
||||
}
|
||||
|
||||
public async Task<ApiKeyRecord> GetUserSelectedApiKey(string userId)
|
||||
{
|
||||
using var conn = CreateConnection();
|
||||
return await conn.QuerySingleOrDefaultAsync<ApiKeyRecord>(@"
|
||||
SELECT k.* FROM api_keys k
|
||||
INNER JOIN users u ON u.selected_api_key_id = k.key_id
|
||||
WHERE u.user_id = @userId",
|
||||
new { userId });
|
||||
}
|
||||
|
||||
}
|
||||
@@ -341,6 +341,10 @@ public partial class DatabaseService
|
||||
TryAddColumn(conn, "api_keys", "usage_5h", "REAL");
|
||||
TryAddColumn(conn, "api_keys", "checked_at", "TEXT");
|
||||
TryAddColumn(conn, "api_keys", "source_url", "TEXT");
|
||||
TryAddColumn(conn, "api_keys", "is_public", "INTEGER NOT NULL DEFAULT 0");
|
||||
|
||||
// users: ausgewählter API-Key
|
||||
TryAddColumn(conn, "users", "selected_api_key_id", "TEXT");
|
||||
|
||||
// Migration: Bestehende PNG/JPEG-Avatare/Images zu WebP konvertieren
|
||||
MigrateImagesToWebP(conn);
|
||||
|
||||
@@ -80,7 +80,7 @@ public class TelegramService
|
||||
return;
|
||||
}
|
||||
|
||||
await HandleMessage(chatId, text, bot.BotToken, session);
|
||||
await HandleMessage(chatId, text, bot.BotToken, bot.UserId, session);
|
||||
}
|
||||
|
||||
private async Task HandleStart(long chatId, TelegramBotRecord bot, SessionRecord session)
|
||||
@@ -159,15 +159,24 @@ public class TelegramService
|
||||
|
||||
// ── Nachricht an Claude weiterleiten ──
|
||||
|
||||
private async Task HandleMessage(long chatId, string userText, string botToken, SessionRecord session)
|
||||
private async Task HandleMessage(long chatId, string userText, string botToken, string botUserId, SessionRecord session)
|
||||
{
|
||||
var ctx = await _chatService.PrepareContext(session.SessionId, null, _maxIterations);
|
||||
var ctx = await _chatService.PrepareContext(session.SessionId, botUserId, _maxIterations);
|
||||
if (ctx == null)
|
||||
{
|
||||
await SendMessage(chatId, botToken, "❌ Kein Agent konfiguriert. Bitte weise der Session im Web-UI einen Agent zu.");
|
||||
return;
|
||||
}
|
||||
|
||||
// API-Key des Bot-Owners auflösen
|
||||
var userApiKey = await _db.GetUserSelectedApiKey(botUserId);
|
||||
if (userApiKey == null)
|
||||
{
|
||||
await SendMessage(chatId, botToken, "❌ Kein API-Key ausgewählt. Der Bot-Besitzer muss im Web-UI einen Key auswählen.");
|
||||
return;
|
||||
}
|
||||
ctx.ApiToken = userApiKey.ApiKey;
|
||||
|
||||
await SendChatAction(chatId, botToken, "typing");
|
||||
|
||||
try
|
||||
|
||||
@@ -391,7 +391,7 @@ 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, string userId = null, TelegramService telegramService = null)
|
||||
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, string userId = null, TelegramService telegramService = null, string apiToken = null)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
@@ -765,9 +765,10 @@ public class ToolService
|
||||
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: null);
|
||||
var ctx = await chatService.PrepareContext(targetSessionId, userId: userId);
|
||||
if (ctx == null)
|
||||
return $"Fehler: Session '{targetSession.Name}' ist nicht konfiguriert (kein Agent zugewiesen?)";
|
||||
ctx.ApiToken = apiToken;
|
||||
|
||||
// History aufbauen – User-Message NICHT in DB speichern (wie Cronjob)
|
||||
// Stattdessen als kontextualisierte Nachricht in die History injizieren
|
||||
|
||||
Reference in new issue
Block a user