feat: Claude CLI Pfad mit ask_user_choice, Plan-Modus und Tool-Konfiguration

- ClaudeCliService: Marker-basierte ask_user_choice Erkennung im Stream-Parser
- AnthropicService: StreamChatViaCli mit allowUserChoice, planMode, webSearch
- BuildHistoryContext erweitert für tool_use/tool_result (Choice-Antworten)
- Tool-Use Records werden in DB persistiert (Poll-Kompatibilität)
- CLI Tools konfigurierbar: Workspace/Plan-Modus/WebSearch/kein Workspace
- OAuth DB-Felder für zukünftige Token-Verwaltung

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MelekiandClaude Opus 4.6 committed 2026-03-17 22:25:39 +01:00
1 parent 8150b58110
commit 4af9430466
7 files changed
+1039 -3

No files matched your search

+57
View File
@@ -563,4 +563,61 @@ public partial class DatabaseService
$"SELECT COUNT(*) FROM scanner_logs {where}",
new { type, scanner });
}
// ── OAuth Tokens ──
/// <summary>
/// Erstellt einen neuen API-Key-Eintrag für OAuth-Tokens.
/// </summary>
public async Task<string> CreateOAuthApiKey(string provider, string name, string accessToken, string refreshToken, DateTime expiresAt, string oauthProvider)
{
using var conn = CreateConnection();
var keyId = Guid.NewGuid().ToString("N");
var now = DateTime.UtcNow.ToString("o");
await conn.ExecuteAsync(@"
INSERT INTO api_keys (key_id, provider, name, api_key, is_public, status, plan, refresh_token, access_token_expires_at, oauth_provider, created_at, updated_at)
VALUES (@keyId, @provider, @name, @accessToken, 0, 'valid', NULL, @refreshToken, @expiresAt, @oauthProvider, @now, @now)",
new { keyId, provider, name, accessToken, refreshToken, expiresAt = expiresAt.ToString("o"), oauthProvider, now });
return keyId;
}
/// <summary>
/// Aktualisiert Access-Token und Ablaufzeit eines OAuth-Keys.
/// </summary>
public async Task UpdateOAuthTokens(string keyId, string newAccessToken, DateTime newExpiresAt)
{
using var conn = CreateConnection();
var now = DateTime.UtcNow.ToString("o");
await conn.ExecuteAsync(@"
UPDATE api_keys SET api_key = @newAccessToken, access_token_expires_at = @expiresAt, updated_at = @now
WHERE key_id = @keyId",
new { keyId, newAccessToken, expiresAt = newExpiresAt.ToString("o"), now });
}
/// <summary>
/// Holt alle OAuth-Keys die bald ablaufen (innerhalb von refreshBeforeMinutes).
/// </summary>
public async Task<IEnumerable<ApiKeyRecord>> GetOAuthKeysNeedingRefresh(int refreshBeforeMinutes = 60)
{
using var conn = CreateConnection();
var threshold = DateTime.UtcNow.AddMinutes(refreshBeforeMinutes).ToString("o");
return await conn.QueryAsync<ApiKeyRecord>(@"
SELECT * FROM api_keys
WHERE refresh_token IS NOT NULL AND refresh_token != ''
AND access_token_expires_at IS NOT NULL AND access_token_expires_at != ''
AND access_token_expires_at < @threshold",
new { threshold });
}
/// <summary>
/// Setzt den Status eines API-Keys auf "expired".
/// </summary>
public async Task SetApiKeyExpired(string keyId)
{
using var conn = CreateConnection();
var now = DateTime.UtcNow.ToString("o");
await conn.ExecuteAsync(
"UPDATE api_keys SET status = 'expired', updated_at = @now WHERE key_id = @keyId",
new { keyId, now });
}
}