feat: API Key pro Agent statt pro User
- Neue Tabelle agent_api_keys (user_id, agent_id, key_id) - Migration: bestehende selected_api_key_id auf alle Agents verteilt - Backend: GetAgentApiKeyRecord(userId, agentId) in Chat, Telegram, Cronjobs - Endpoints: select/selected mit ?agentId Query-Parameter - Frontend: currentAgentId aus Session, alle Key-Calls agent-aware - DeleteApiKey bereinigt agent_api_keys statt users.selected_api_key_id
This commit is contained in:
1 parent
b04b1a2cf9
commit
1effc6d00f
10 files changed
+128
-43
No files matched your search
@@ -540,8 +540,16 @@ public static class ApiKeysEndpoint
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.SetUserSelectedApiKey(userId, keyId);
|
var agentId = ctx.Request.Query["agentId"].FirstOrDefault();
|
||||||
await ctx.Response.WriteAsJsonAsync(new { ok = true, keyId, name = key.Name });
|
if (string.IsNullOrEmpty(agentId))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 400;
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { error = "agentId required" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.SetAgentApiKey(userId, agentId, keyId);
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { ok = true, keyId, agentId, name = key.Name });
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task HandleGetSelected(HttpContext ctx, DatabaseService db)
|
private static async Task HandleGetSelected(HttpContext ctx, DatabaseService db)
|
||||||
@@ -549,7 +557,14 @@ public static class ApiKeysEndpoint
|
|||||||
var userId = ctx.GetUserId();
|
var userId = ctx.GetUserId();
|
||||||
if (userId == null) { ctx.Response.StatusCode = 401; return; }
|
if (userId == null) { ctx.Response.StatusCode = 401; return; }
|
||||||
|
|
||||||
var key = await db.GetUserSelectedApiKey(userId);
|
var agentId = ctx.Request.Query["agentId"].FirstOrDefault();
|
||||||
|
if (string.IsNullOrEmpty(agentId))
|
||||||
|
{
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { selected = false });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = await db.GetAgentApiKeyRecord(userId, agentId);
|
||||||
if (key == null)
|
if (key == null)
|
||||||
{
|
{
|
||||||
await ctx.Response.WriteAsJsonAsync(new { selected = false });
|
await ctx.Response.WriteAsJsonAsync(new { selected = false });
|
||||||
|
|||||||
@@ -51,15 +51,6 @@ public static class ChatEndpoint
|
|||||||
|
|
||||||
var userId = req.HttpContext.GetUserId();
|
var userId = req.HttpContext.GetUserId();
|
||||||
|
|
||||||
// API-Key des Users auflösen
|
|
||||||
var userApiKey = await db.GetUserSelectedApiKey(userId);
|
|
||||||
if (userApiKey == null)
|
|
||||||
{
|
|
||||||
res.StatusCode = 400;
|
|
||||||
await res.WriteAsJsonAsync(new { error = "Kein API-Key ausgewählt. Bitte wähle einen Key aus." });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var maxIterations = settings.GetInt("MaxIterations", 100);
|
var maxIterations = settings.GetInt("MaxIterations", 100);
|
||||||
|
|
||||||
var hasRemote = remoteMgr.GetBySessionId(sessionId) != null;
|
var hasRemote = remoteMgr.GetBySessionId(sessionId) != null;
|
||||||
@@ -71,6 +62,15 @@ public static class ChatEndpoint
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// API-Key des Users für diesen Agent auflösen
|
||||||
|
var userApiKey = await db.GetAgentApiKeyRecord(userId, ctx.AgentId);
|
||||||
|
if (userApiKey == null)
|
||||||
|
{
|
||||||
|
res.StatusCode = 400;
|
||||||
|
await res.WriteAsJsonAsync(new { error = "Kein API-Key für diesen Agent zugewiesen. Bitte weise einen Key zu." });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isChoiceResponse)
|
if (isChoiceResponse)
|
||||||
{
|
{
|
||||||
// Choice-Antwort: tool_result in die VORHERIGE Chain speichern (wo der tool_use liegt)
|
// Choice-Antwort: tool_result in die VORHERIGE Chain speichern (wo der tool_use liegt)
|
||||||
|
|||||||
@@ -86,10 +86,10 @@ public class CronjobService : BackgroundService
|
|||||||
if (ctx == null)
|
if (ctx == null)
|
||||||
throw new Exception("Session oder Agent nicht gefunden / nicht konfiguriert");
|
throw new Exception("Session oder Agent nicht gefunden / nicht konfiguriert");
|
||||||
|
|
||||||
// API-Key des Cronjob-Owners auflösen
|
// API-Key des Cronjob-Owners für diesen Agent auflösen
|
||||||
var userApiKey = await _db.GetUserSelectedApiKey(job.UserId);
|
var userApiKey = await _db.GetAgentApiKeyRecord(job.UserId, ctx.AgentId);
|
||||||
if (userApiKey == null)
|
if (userApiKey == null)
|
||||||
throw new Exception($"Kein API-Key für User {job.UserId} ausgewählt");
|
throw new Exception($"Kein API-Key für Agent {ctx.AgentId} zugewiesen (User {job.UserId})");
|
||||||
ctx.ApiToken = userApiKey.ApiKey;
|
ctx.ApiToken = userApiKey.ApiKey;
|
||||||
|
|
||||||
// History aus DB laden
|
// History aus DB laden
|
||||||
@@ -140,11 +140,11 @@ public class CronjobService : BackgroundService
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// API-Key des Cronjob-Owners auflösen
|
// API-Key des Cronjob-Owners für Notify-Agent auflösen
|
||||||
var notifyApiKey = await _db.GetUserSelectedApiKey(job.UserId);
|
var notifyApiKey = await _db.GetAgentApiKeyRecord(job.UserId, notifyCtx.AgentId);
|
||||||
if (notifyApiKey == null)
|
if (notifyApiKey == null)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Kein API-Key für User {UserId} (Cronjob '{Name}' Notification)", job.UserId, job.Name);
|
_logger.LogWarning("Kein API-Key für Agent {AgentId} (Cronjob '{Name}' Notification)", notifyCtx.AgentId, job.Name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
notifyCtx.ApiToken = notifyApiKey.ApiKey;
|
notifyCtx.ApiToken = notifyApiKey.ApiKey;
|
||||||
|
|||||||
@@ -427,9 +427,9 @@ public partial class DatabaseService
|
|||||||
await conn.ExecuteAsync(
|
await conn.ExecuteAsync(
|
||||||
"UPDATE scanned_keys SET adopted_api_key_id = NULL WHERE adopted_api_key_id = @keyId",
|
"UPDATE scanned_keys SET adopted_api_key_id = NULL WHERE adopted_api_key_id = @keyId",
|
||||||
new { keyId });
|
new { keyId });
|
||||||
// User die diesen Key ausgewählt haben zurücksetzen
|
// Agent-Key-Zuordnungen die auf diesen Key verweisen löschen
|
||||||
await conn.ExecuteAsync(
|
await conn.ExecuteAsync(
|
||||||
"UPDATE users SET selected_api_key_id = NULL WHERE selected_api_key_id = @keyId",
|
"DELETE FROM agent_api_keys WHERE key_id = @keyId",
|
||||||
new { keyId });
|
new { keyId });
|
||||||
await conn.ExecuteAsync("DELETE FROM api_keys WHERE key_id = @keyId", new { keyId });
|
await conn.ExecuteAsync("DELETE FROM api_keys WHERE key_id = @keyId", new { keyId });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -187,32 +187,43 @@ public partial class DatabaseService
|
|||||||
await conn.ExecuteAsync("DELETE FROM users WHERE user_id = @userId", new { userId });
|
await conn.ExecuteAsync("DELETE FROM users WHERE user_id = @userId", new { userId });
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── User API-Key Auswahl ──
|
// ── Agent API-Key Zuordnung ──
|
||||||
|
|
||||||
public async Task SetUserSelectedApiKey(string userId, string keyId)
|
public async Task SetAgentApiKey(string userId, string agentId, string keyId)
|
||||||
{
|
{
|
||||||
using var conn = CreateConnection();
|
using var conn = CreateConnection();
|
||||||
await conn.ExecuteAsync(
|
if (string.IsNullOrEmpty(keyId))
|
||||||
"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") });
|
await conn.ExecuteAsync(
|
||||||
|
"DELETE FROM agent_api_keys WHERE user_id = @userId AND agent_id = @agentId",
|
||||||
|
new { userId, agentId });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await conn.ExecuteAsync(@"
|
||||||
|
INSERT INTO agent_api_keys (user_id, agent_id, key_id)
|
||||||
|
VALUES (@userId, @agentId, @keyId)
|
||||||
|
ON CONFLICT(user_id, agent_id) DO UPDATE SET key_id = @keyId",
|
||||||
|
new { userId, agentId, keyId });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetUserSelectedApiKeyId(string userId)
|
public async Task<string> GetAgentApiKeyId(string userId, string agentId)
|
||||||
{
|
{
|
||||||
using var conn = CreateConnection();
|
using var conn = CreateConnection();
|
||||||
return await conn.QuerySingleOrDefaultAsync<string>(
|
return await conn.QuerySingleOrDefaultAsync<string>(
|
||||||
"SELECT selected_api_key_id FROM users WHERE user_id = @userId",
|
"SELECT key_id FROM agent_api_keys WHERE user_id = @userId AND agent_id = @agentId",
|
||||||
new { userId });
|
new { userId, agentId });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ApiKeyRecord> GetUserSelectedApiKey(string userId)
|
public async Task<ApiKeyRecord> GetAgentApiKeyRecord(string userId, string agentId)
|
||||||
{
|
{
|
||||||
using var conn = CreateConnection();
|
using var conn = CreateConnection();
|
||||||
return await conn.QuerySingleOrDefaultAsync<ApiKeyRecord>(@"
|
return await conn.QuerySingleOrDefaultAsync<ApiKeyRecord>(@"
|
||||||
SELECT k.* FROM api_keys k
|
SELECT k.* FROM api_keys k
|
||||||
INNER JOIN users u ON u.selected_api_key_id = k.key_id
|
INNER JOIN agent_api_keys a ON a.key_id = k.key_id
|
||||||
WHERE u.user_id = @userId",
|
WHERE a.user_id = @userId AND a.agent_id = @agentId",
|
||||||
new { userId });
|
new { userId, agentId });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -357,9 +357,23 @@ public partial class DatabaseService
|
|||||||
TryAddColumn(conn, "api_keys", "source_url", "TEXT");
|
TryAddColumn(conn, "api_keys", "source_url", "TEXT");
|
||||||
TryAddColumn(conn, "api_keys", "is_public", "INTEGER NOT NULL DEFAULT 0");
|
TryAddColumn(conn, "api_keys", "is_public", "INTEGER NOT NULL DEFAULT 0");
|
||||||
|
|
||||||
// users: ausgewählter API-Key
|
// users: ausgewählter API-Key (legacy, wird durch agent_api_keys ersetzt)
|
||||||
TryAddColumn(conn, "users", "selected_api_key_id", "TEXT");
|
TryAddColumn(conn, "users", "selected_api_key_id", "TEXT");
|
||||||
|
|
||||||
|
conn.Execute(@"
|
||||||
|
CREATE TABLE IF NOT EXISTS agent_api_keys (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
agent_id TEXT NOT NULL,
|
||||||
|
key_id TEXT NOT NULL,
|
||||||
|
UNIQUE(user_id, agent_id)
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_api_keys_user ON agent_api_keys(user_id);
|
||||||
|
");
|
||||||
|
|
||||||
|
// Migration: selected_api_key_id → agent_api_keys (einmalig)
|
||||||
|
MigrateSelectedApiKeysToAgentKeys(conn);
|
||||||
|
|
||||||
// Migration: Bestehende PNG/JPEG-Avatare/Images zu WebP konvertieren
|
// Migration: Bestehende PNG/JPEG-Avatare/Images zu WebP konvertieren
|
||||||
MigrateImagesToWebP(conn);
|
MigrateImagesToWebP(conn);
|
||||||
|
|
||||||
@@ -367,6 +381,43 @@ public partial class DatabaseService
|
|||||||
MigrateImageThumbnails(conn);
|
MigrateImageThumbnails(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void MigrateSelectedApiKeysToAgentKeys(SqliteConnection conn)
|
||||||
|
{
|
||||||
|
// Nur migrieren wenn agent_api_keys leer ist und users mit selected_api_key_id existieren
|
||||||
|
var count = conn.ExecuteScalar<int>("SELECT COUNT(*) FROM agent_api_keys");
|
||||||
|
if (count > 0) return;
|
||||||
|
|
||||||
|
var usersWithKeys = conn.Query<(string UserId, string SelectedApiKeyId)>(
|
||||||
|
"SELECT user_id, selected_api_key_id FROM users WHERE selected_api_key_id IS NOT NULL AND selected_api_key_id != ''")
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var (userId, keyId) in usersWithKeys)
|
||||||
|
{
|
||||||
|
// Alle Agents des Users + öffentliche Agents
|
||||||
|
var agentIds = conn.Query<string>(
|
||||||
|
"SELECT agent_id FROM agents WHERE user_id = @userId OR is_public = 1",
|
||||||
|
new { userId }).ToList();
|
||||||
|
|
||||||
|
// Alle Sessions des Users mit Agent → deren Agents auch berücksichtigen
|
||||||
|
var sessionAgentIds = conn.Query<string>(
|
||||||
|
"SELECT DISTINCT agent_id FROM sessions WHERE user_id = @userId AND agent_id IS NOT NULL AND agent_id != ''",
|
||||||
|
new { userId }).ToList();
|
||||||
|
|
||||||
|
var allAgentIds = agentIds.Union(sessionAgentIds).Distinct().ToList();
|
||||||
|
|
||||||
|
foreach (var agentId in allAgentIds)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
conn.Execute(
|
||||||
|
"INSERT OR IGNORE INTO agent_api_keys (user_id, agent_id, key_id) VALUES (@userId, @agentId, @keyId)",
|
||||||
|
new { userId, agentId, keyId });
|
||||||
|
}
|
||||||
|
catch { /* UNIQUE constraint – already exists */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void MigrateImagesToWebP(SqliteConnection conn)
|
private void MigrateImagesToWebP(SqliteConnection conn)
|
||||||
{
|
{
|
||||||
const int webPQuality = 80;
|
const int webPQuality = 80;
|
||||||
|
|||||||
@@ -168,11 +168,11 @@ public class TelegramService
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// API-Key des Bot-Owners auflösen
|
// API-Key des Bot-Owners für diesen Agent auflösen
|
||||||
var userApiKey = await _db.GetUserSelectedApiKey(botUserId);
|
var userApiKey = await _db.GetAgentApiKeyRecord(botUserId, ctx.AgentId);
|
||||||
if (userApiKey == null)
|
if (userApiKey == null)
|
||||||
{
|
{
|
||||||
await SendMessage(chatId, botToken, "❌ Kein API-Key ausgewählt. Der Bot-Besitzer muss im Web-UI einen Key auswählen.");
|
await SendMessage(chatId, botToken, "❌ Kein API-Key für diesen Agent zugewiesen. Der Bot-Besitzer muss im Web-UI einen Key zuweisen.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ctx.ApiToken = userApiKey.ApiKey;
|
ctx.ApiToken = userApiKey.ApiKey;
|
||||||
|
|||||||
+2
-2
@@ -224,8 +224,8 @@ function apiToggleApiKeyPublic(id) { return _put('/api/admin/api-keys/' + id + '
|
|||||||
// ── User Key Selection API ──
|
// ── User Key Selection API ──
|
||||||
|
|
||||||
function apiGetAvailableKeys() { return _get('/api/api-keys/available', []); }
|
function apiGetAvailableKeys() { return _get('/api/api-keys/available', []); }
|
||||||
function apiSelectApiKey(keyId) { return _put('/api/api-keys/select/' + keyId, undefined, 'Fehler beim Auswählen'); }
|
function apiSelectApiKey(keyId, agentId) { return _put('/api/api-keys/select/' + keyId + '?agentId=' + encodeURIComponent(agentId), undefined, 'Fehler beim Auswählen'); }
|
||||||
function apiGetSelectedKey() { return _get('/api/api-keys/selected', null); }
|
function apiGetSelectedKey(agentId) { return _get('/api/api-keys/selected' + (agentId ? '?agentId=' + encodeURIComponent(agentId) : ''), null); }
|
||||||
function apiDeleteApiKey(id) { return _del('/api/admin/api-keys/' + id, 'Fehler beim Löschen'); }
|
function apiDeleteApiKey(id) { return _del('/api/admin/api-keys/' + id, 'Fehler beim Löschen'); }
|
||||||
function apiCheckApiKey(id) { return _post('/api/admin/api-keys/' + id + '/check', undefined, 'Fehler beim Prüfen'); }
|
function apiCheckApiKey(id) { return _post('/api/admin/api-keys/' + id + '/check', undefined, 'Fehler beim Prüfen'); }
|
||||||
function apiCheckAllApiKeys() { return _post('/api/admin/api-keys/check-all', undefined, 'Fehler beim Prüfen'); }
|
function apiCheckAllApiKeys() { return _post('/api/admin/api-keys/check-all', undefined, 'Fehler beim Prüfen'); }
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ async function _initChatUsageBadge() {
|
|||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var selectedKey = await apiGetSelectedKey();
|
var selectedKey = await apiGetSelectedKey(getCurrentAgentId());
|
||||||
if (!selectedKey || !selectedKey.selected) {
|
if (!selectedKey || !selectedKey.selected) {
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
var noKeyBtn = document.createElement('button');
|
var noKeyBtn = document.createElement('button');
|
||||||
@@ -85,7 +85,7 @@ async function _refreshChatUsageBadge() {
|
|||||||
var container = document.getElementById('chatUsageBadge');
|
var container = document.getElementById('chatUsageBadge');
|
||||||
if (!container || !_chatUsageKeyId) return;
|
if (!container || !_chatUsageKeyId) return;
|
||||||
try {
|
try {
|
||||||
var selectedKey = await apiGetSelectedKey();
|
var selectedKey = await apiGetSelectedKey(getCurrentAgentId());
|
||||||
if (selectedKey) _renderChatUsageBadge(container, selectedKey);
|
if (selectedKey) _renderChatUsageBadge(container, selectedKey);
|
||||||
} catch(e) { /* ignore */ }
|
} catch(e) { /* ignore */ }
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ function _renderChatUsageBadge(container, key) {
|
|||||||
checkBtn.querySelector('i').className = 'fa fa-spinner fa-spin';
|
checkBtn.querySelector('i').className = 'fa fa-spinner fa-spin';
|
||||||
try {
|
try {
|
||||||
await apiCheckApiKey(_chatUsageKeyId);
|
await apiCheckApiKey(_chatUsageKeyId);
|
||||||
var selectedKey = await apiGetSelectedKey();
|
var selectedKey = await apiGetSelectedKey(getCurrentAgentId());
|
||||||
if (selectedKey) _renderChatUsageBadge(container, selectedKey);
|
if (selectedKey) _renderChatUsageBadge(container, selectedKey);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error('Key check failed:', e);
|
console.error('Key check failed:', e);
|
||||||
@@ -136,7 +136,7 @@ async function _showKeySwitchModal() {
|
|||||||
try { keys = await apiGetAvailableKeys(); } catch(e) { return; }
|
try { keys = await apiGetAvailableKeys(); } catch(e) { return; }
|
||||||
|
|
||||||
var selectedKey = null;
|
var selectedKey = null;
|
||||||
try { selectedKey = await apiGetSelectedKey(); } catch(e) { /* ignore */ }
|
try { selectedKey = await apiGetSelectedKey(getCurrentAgentId()); } catch(e) { /* ignore */ }
|
||||||
var selectedKeyId = selectedKey ? selectedKey.keyId : null;
|
var selectedKeyId = selectedKey ? selectedKey.keyId : null;
|
||||||
|
|
||||||
var { body, close } = _createModal('API Key wechseln', { modal: 'key-switch-modal' });
|
var { body, close } = _createModal('API Key wechseln', { modal: 'key-switch-modal' });
|
||||||
@@ -187,7 +187,7 @@ async function _showKeySwitchModal() {
|
|||||||
row.addEventListener('click', async function() {
|
row.addEventListener('click', async function() {
|
||||||
row.classList.add('loading-state');
|
row.classList.add('loading-state');
|
||||||
try {
|
try {
|
||||||
await apiSelectApiKey(key.keyId);
|
await apiSelectApiKey(key.keyId, getCurrentAgentId());
|
||||||
close();
|
close();
|
||||||
await _initChatUsageBadge();
|
await _initChatUsageBadge();
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
@@ -352,6 +352,8 @@ registerPage('chat', {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentAgentId = session.agentId;
|
||||||
|
|
||||||
const titleEl = document.getElementById('sessionTitle');
|
const titleEl = document.getElementById('sessionTitle');
|
||||||
if (titleEl) {
|
if (titleEl) {
|
||||||
titleEl.textContent = session.name || 'Unbenannt';
|
titleEl.textContent = session.name || 'Unbenannt';
|
||||||
@@ -605,6 +607,7 @@ registerPage('chat', {
|
|||||||
_chatAbandoned = true;
|
_chatAbandoned = true;
|
||||||
stopTypingIndicator();
|
stopTypingIndicator();
|
||||||
setCurrentAgentName(null);
|
setCurrentAgentName(null);
|
||||||
|
currentAgentId = null;
|
||||||
closeEmojiPicker();
|
closeEmojiPicker();
|
||||||
// Usage Badge cleanup
|
// Usage Badge cleanup
|
||||||
if (_chatUsageScrollHandler) {
|
if (_chatUsageScrollHandler) {
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
// ── Session State ──
|
// ── Session State ──
|
||||||
|
|
||||||
let currentSessionId = null;
|
let currentSessionId = null;
|
||||||
|
let currentAgentId = null;
|
||||||
|
|
||||||
function getCurrentSessionId() {
|
function getCurrentSessionId() {
|
||||||
return currentSessionId;
|
return currentSessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCurrentAgentId() {
|
||||||
|
return currentAgentId;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user