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() {