From 5aae72a5e5b73f92cf8a80d174aa55293e474979 Mon Sep 17 00:00:00 2001 From: Timothy Carambat Date: Tue, 14 Apr 2026 14:46:54 -0700 Subject: [PATCH] Refactor Gmail Agent (#5439) --- frontend/src/models/gmailAgent.js | 22 +++++++ .../Admin/Agents/GMailSkillPanel/index.jsx | 51 +++++++++-------- server/endpoints/admin.js | 6 -- server/endpoints/utils/gmailAgentUtils.js | 39 +++++++++++++ server/index.js | 2 + server/models/systemSettings.js | 46 +++++++++------ .../utils/agents/aibitat/plugins/gmail/lib.js | 57 ++++++++++++------- 7 files changed, 155 insertions(+), 68 deletions(-) create mode 100644 frontend/src/models/gmailAgent.js create mode 100644 server/endpoints/utils/gmailAgentUtils.js diff --git a/frontend/src/models/gmailAgent.js b/frontend/src/models/gmailAgent.js new file mode 100644 index 00000000..32b3f057 --- /dev/null +++ b/frontend/src/models/gmailAgent.js @@ -0,0 +1,22 @@ +import { API_BASE } from "@/utils/constants"; +import { baseHeaders } from "@/utils/request"; + +const GmailAgent = { + /** + * Get the current configuration status for Gmail. + * @returns {Promise<{success: boolean, isConfigured?: boolean, config?: {deploymentId: string, apiKey: string}, error?: string}>} + */ + getStatus: async () => { + return await fetch(`${API_BASE}/admin/agent-skills/gmail/status`, { + method: "GET", + headers: baseHeaders(), + }) + .then((res) => res.json()) + .catch((e) => { + console.error(e); + return { success: false, error: e.message }; + }); + }, +}; + +export default GmailAgent; diff --git a/frontend/src/pages/Admin/Agents/GMailSkillPanel/index.jsx b/frontend/src/pages/Admin/Agents/GMailSkillPanel/index.jsx index bb6c0f25..d0bbc2dd 100644 --- a/frontend/src/pages/Admin/Agents/GMailSkillPanel/index.jsx +++ b/frontend/src/pages/Admin/Agents/GMailSkillPanel/index.jsx @@ -19,6 +19,7 @@ import { import GMailIcon from "./gmail.png"; import Admin from "@/models/admin"; import System from "@/models/system"; +import GmailAgent from "@/models/gmailAgent"; import { getGmailSkills, filterSkillCategories } from "./utils"; import { Tooltip } from "react-tooltip"; import { Link } from "react-router-dom"; @@ -46,22 +47,21 @@ export default function GMailSkillPanel({ useEffect(() => { setLoading(true); Promise.all([ - Admin.systemPreferencesByFields([ - "disabled_gmail_skills", - "gmail_deployment_id", - "gmail_api_key", - ]), + Admin.systemPreferencesByFields(["disabled_gmail_skills"]), System.keys(), + GmailAgent.getStatus(), ]) - .then(([prefsRes, settingsRes]) => { - const loadedDeploymentId = - prefsRes?.settings?.gmail_deployment_id ?? ""; - const loadedApiKey = prefsRes?.settings?.gmail_api_key ?? ""; + .then(([prefsRes, settingsRes, statusRes]) => { setDisabledSkills(prefsRes?.settings?.disabled_gmail_skills ?? []); - setDeploymentId(loadedDeploymentId); - setApiKey(loadedApiKey); setIsMultiUserMode(settingsRes?.MultiUserMode ?? false); - setConfigDefaultExpanded(!(loadedDeploymentId && loadedApiKey)); + + if (statusRes?.success && statusRes.config) { + const loadedDeploymentId = statusRes.config.deploymentId || ""; + const loadedApiKey = statusRes.config.apiKey || ""; + setDeploymentId(loadedDeploymentId); + setApiKey(loadedApiKey); + setConfigDefaultExpanded(!(loadedDeploymentId && loadedApiKey)); + } }) .catch(() => { setDisabledSkills([]); @@ -73,15 +73,16 @@ export default function GMailSkillPanel({ useEffect(() => { if (prevHasChanges.current === true && hasChanges === false) { - Admin.systemPreferencesByFields([ - "disabled_gmail_skills", - "gmail_deployment_id", - "gmail_api_key", + Promise.all([ + Admin.systemPreferencesByFields(["disabled_gmail_skills"]), + GmailAgent.getStatus(), ]) - .then((res) => { - setDisabledSkills(res?.settings?.disabled_gmail_skills ?? []); - setDeploymentId(res?.settings?.gmail_deployment_id ?? ""); - setApiKey(res?.settings?.gmail_api_key ?? ""); + .then(([prefsRes, statusRes]) => { + setDisabledSkills(prefsRes?.settings?.disabled_gmail_skills ?? []); + if (statusRes?.success && statusRes.config) { + setDeploymentId(statusRes.config.deploymentId || ""); + setApiKey(statusRes.config.apiKey || ""); + } }) .catch(() => {}); } @@ -425,6 +426,11 @@ function SkillRow({ skill, disabled, onToggle }) { } function HiddenFormInputs({ disabledSkills, deploymentId, apiKey }) { + const configJson = JSON.stringify({ + deploymentId: deploymentId || "", + apiKey: apiKey || "", + }); + return ( <> - ); } diff --git a/server/endpoints/admin.js b/server/endpoints/admin.js index d4e6758e..e0fc1975 100644 --- a/server/endpoints/admin.js +++ b/server/endpoints/admin.js @@ -415,12 +415,6 @@ function adminEndpoints(app) { case "disabled_outlook_skills": requestedSettings[label] = safeJsonParse(setting?.value, []); break; - case "gmail_deployment_id": - requestedSettings[label] = setting?.value || null; - break; - case "gmail_api_key": - requestedSettings[label] = setting?.value || null; - break; case "imported_agent_skills": requestedSettings[label] = ImportedPlugin.listImportedPlugins(); break; diff --git a/server/endpoints/utils/gmailAgentUtils.js b/server/endpoints/utils/gmailAgentUtils.js new file mode 100644 index 00000000..2433a0d2 --- /dev/null +++ b/server/endpoints/utils/gmailAgentUtils.js @@ -0,0 +1,39 @@ +const { + isSingleUserMode, +} = require("../../utils/middleware/multiUserProtected"); +const { validatedRequest } = require("../../utils/middleware/validatedRequest"); +const { GmailBridge } = require("../../utils/agents/aibitat/plugins/gmail/lib"); + +function gmailAgentEndpoints(app) { + if (!app) return; + + app.get( + "/admin/agent-skills/gmail/status", + [validatedRequest, isSingleUserMode], + async (_request, response) => { + try { + const config = await GmailBridge.getConfig(); + + const hasDeploymentId = !!config.deploymentId; + const hasApiKey = !!config.apiKey; + const isConfigured = hasDeploymentId && hasApiKey; + + const safeConfig = { + deploymentId: config.deploymentId || "", + apiKey: hasApiKey ? "********" : "", + }; + + return response.status(200).json({ + success: true, + isConfigured, + config: safeConfig, + }); + } catch (e) { + console.error("Gmail status error:", e); + response.status(500).json({ success: false, error: e.message }); + } + } + ); +} + +module.exports = { gmailAgentEndpoints }; diff --git a/server/index.js b/server/index.js index b2914761..295f0cfe 100644 --- a/server/index.js +++ b/server/index.js @@ -38,6 +38,7 @@ const { telegramEndpoints } = require("./endpoints/telegram"); const { outlookAgentEndpoints, } = require("./endpoints/utils/outlookAgentUtils"); +const { gmailAgentEndpoints } = require("./endpoints/utils/gmailAgentUtils"); const { httpLogger } = require("./middleware/httpLogger"); const app = express(); const apiRouter = express.Router(); @@ -93,6 +94,7 @@ mobileEndpoints(apiRouter); webPushEndpoints(apiRouter); telegramEndpoints(apiRouter); outlookAgentEndpoints(apiRouter); +gmailAgentEndpoints(apiRouter); // Externally facing embedder endpoints embeddedEndpoints(apiRouter); diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index e5cdb296..ac927570 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -51,8 +51,7 @@ const SystemSettings = { "disabled_filesystem_skills", "disabled_create_files_skills", "disabled_gmail_skills", - "gmail_deployment_id", - "gmail_api_key", + "gmail_agent_config", "disabled_outlook_skills", "outlook_agent_config", "imported_agent_skills", @@ -75,8 +74,7 @@ const SystemSettings = { "disabled_filesystem_skills", "disabled_create_files_skills", "disabled_gmail_skills", - "gmail_deployment_id", - "gmail_api_key", + "gmail_agent_config", "disabled_outlook_skills", "outlook_agent_config", "agent_sql_connections", @@ -208,21 +206,33 @@ const SystemSettings = { return JSON.stringify([]); } }, - gmail_deployment_id: (update) => { + gmail_agent_config: async (update) => { + const GmailBridge = require("../utils/agents/aibitat/plugins/gmail/lib"); try { - if (!update || typeof update !== "string") return null; - return String(update).trim(); + if (!update) return JSON.stringify({}); + + const newConfig = + typeof update === "string" ? safeJsonParse(update, {}) : update; + const existingConfig = safeJsonParse( + (await SystemSettings.get({ label: "gmail_agent_config" }))?.value, + {} + ); + + const mergedConfig = { ...existingConfig }; + + mergeStringField(mergedConfig, newConfig, "deploymentId"); + mergeStringField( + mergedConfig, + newConfig, + "apiKey", + (v) => !v.match(/^\*+$/) + ); + + return JSON.stringify(mergedConfig); + } catch (e) { + console.error(`Could not validate gmail agent config:`, e.message); + return JSON.stringify({}); } finally { - const GmailBridge = require("../utils/agents/aibitat/plugins/gmail/lib"); - GmailBridge.reset(); - } - }, - gmail_api_key: (update) => { - try { - if (!update || typeof update !== "string") return null; - return String(update).trim(); - } finally { - const GmailBridge = require("../utils/agents/aibitat/plugins/gmail/lib"); GmailBridge.reset(); } }, @@ -531,6 +541,8 @@ const SystemSettings = { } } + if (validatedValue === undefined) continue; + updatePromises.push( prisma.system_settings.upsert({ where: { label: key }, diff --git a/server/utils/agents/aibitat/plugins/gmail/lib.js b/server/utils/agents/aibitat/plugins/gmail/lib.js index 711e450e..28e52c27 100644 --- a/server/utils/agents/aibitat/plugins/gmail/lib.js +++ b/server/utils/agents/aibitat/plugins/gmail/lib.js @@ -5,6 +5,7 @@ const mime = require("mime"); const { SystemSettings } = require("../../../../../models/systemSettings"); const { CollectorApi } = require("../../../../collectorApi"); const { humanFileSize } = require("../../../../helpers"); +const { safeJsonParse } = require("../../../../http"); const MAX_TOTAL_ATTACHMENT_SIZE = 20 * 1024 * 1024; // 20MB limit for all attachments combined @@ -222,6 +223,34 @@ class GmailBridge { this.#isInitialized = false; } + /** + * Gets the current Gmail agent configuration from system settings. + * @returns {Promise<{deploymentId?: string, apiKey?: string}>} + */ + static async getConfig() { + const configJson = await SystemSettings.getValueOrFallback( + { label: "gmail_agent_config" }, + "{}" + ); + return safeJsonParse(configJson, {}); + } + + /** + * Updates the Gmail agent configuration in system settings. + * @param {Object} updates - Fields to update + * @returns {Promise<{success: boolean, error?: string}>} + */ + static async updateConfig(updates) { + try { + await SystemSettings.updateSettings({ + gmail_agent_config: JSON.stringify(updates), + }); + return { success: true }; + } catch (error) { + return { success: false, error: error.message }; + } + } + /** * Initializes the Gmail bridge by fetching configuration from system settings. * @returns {Promise<{success: boolean, error?: string}>} @@ -239,16 +268,8 @@ class GmailBridge { }; } - const deploymentId = await SystemSettings.getValueOrFallback( - { label: "gmail_deployment_id" }, - null - ); - const apiKey = await SystemSettings.getValueOrFallback( - { label: "gmail_api_key" }, - null - ); - - if (!deploymentId || !apiKey) { + const config = await GmailBridge.getConfig(); + if (!config.deploymentId || !config.apiKey) { return { success: false, error: @@ -256,8 +277,8 @@ class GmailBridge { }; } - this.#deploymentId = deploymentId; - this.#apiKey = apiKey; + this.#deploymentId = config.deploymentId; + this.#apiKey = config.apiKey; this.#isInitialized = true; return { success: true }; } catch (error) { @@ -282,16 +303,8 @@ class GmailBridge { const isMultiUser = await SystemSettings.isMultiUserMode(); if (isMultiUser) return false; - const deploymentId = await SystemSettings.getValueOrFallback( - { label: "gmail_deployment_id" }, - null - ); - const apiKey = await SystemSettings.getValueOrFallback( - { label: "gmail_api_key" }, - null - ); - - return !!(deploymentId && apiKey); + const config = await GmailBridge.getConfig(); + return !!(config.deploymentId && config.apiKey); } get maskedDeploymentId() {