Refactor Gmail Agent (#5439)

This commit is contained in:
Timothy Carambat 2026-04-14 14:46:54 -07:00 committed by GitHub
parent f17337fb97
commit 5aae72a5e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 155 additions and 68 deletions

View File

@ -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;

View File

@ -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 (
<>
<input
@ -433,11 +439,10 @@ function HiddenFormInputs({ disabledSkills, deploymentId, apiKey }) {
value={disabledSkills.join(",")}
/>
<input
name="system::gmail_deployment_id"
name="system::gmail_agent_config"
type="hidden"
value={deploymentId}
value={configJson}
/>
<input name="system::gmail_api_key" type="hidden" value={apiKey} />
</>
);
}

View File

@ -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;

View File

@ -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 };

View File

@ -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);

View File

@ -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 },

View File

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