n8n
Version:
n8n Workflow Automation Tool
466 lines • 20.3 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var InstanceAiSettingsService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.InstanceAiSettingsService = void 0;
const api_types_1 = require("@n8n/api-types");
const config_1 = require("@n8n/config");
const db_1 = require("@n8n/db");
const di_1 = require("@n8n/di");
const n8n_workflow_1 = require("n8n-workflow");
const credentials_finder_service_1 = require("../../credentials/credentials-finder.service");
const credentials_service_1 = require("../../credentials/credentials.service");
const unprocessable_error_1 = require("../../errors/response-errors/unprocessable.error");
const event_service_1 = require("../../events/event.service");
const ai_service_1 = require("../../services/ai.service");
const user_service_1 = require("../../services/user.service");
const ADMIN_SETTINGS_KEY = 'instanceAi.settings';
const CREDENTIAL_TO_MASTRA_PROVIDER = {
openAiApi: 'openai',
anthropicApi: 'anthropic',
googlePalmApi: 'google',
ollamaApi: 'ollama',
groqApi: 'groq',
deepSeekApi: 'deepseek',
mistralCloudApi: 'mistral',
xAiApi: 'xai',
openRouterApi: 'openrouter',
cohereApi: 'cohere',
};
const SUPPORTED_CREDENTIAL_TYPES = Object.keys(CREDENTIAL_TO_MASTRA_PROVIDER);
const URL_FIELD_MAP = {
openAiApi: 'url',
anthropicApi: 'url',
googlePalmApi: 'host',
ollamaApi: 'baseUrl',
};
const SANDBOX_CREDENTIAL_TYPES = ['daytonaApi', 'httpHeaderAuth'];
const SEARCH_CREDENTIAL_TYPES = ['braveSearchApi', 'searXngApi'];
const SERVICE_CREDENTIAL_TYPES = [...SANDBOX_CREDENTIAL_TYPES, ...SEARCH_CREDENTIAL_TYPES];
let InstanceAiSettingsService = InstanceAiSettingsService_1 = class InstanceAiSettingsService {
constructor(globalConfig, settingsRepository, userRepository, userService, aiService, credentialsService, credentialsFinderService, eventService) {
this.settingsRepository = settingsRepository;
this.userRepository = userRepository;
this.userService = userService;
this.aiService = aiService;
this.credentialsService = credentialsService;
this.credentialsFinderService = credentialsFinderService;
this.eventService = eventService;
this.enabled = true;
this.permissions = { ...api_types_1.DEFAULT_INSTANCE_AI_PERMISSIONS };
this.adminDaytonaCredentialId = null;
this.adminN8nSandboxCredentialId = null;
this.adminSearchCredentialId = null;
this.config = globalConfig.instanceAi;
this.deploymentConfig = globalConfig.deployment;
}
get isCloud() {
return this.deploymentConfig.type === 'cloud';
}
isProxyEnabled() {
return this.aiService.isProxyEnabled();
}
async loadFromDb() {
const row = await this.settingsRepository.findByKey(ADMIN_SETTINGS_KEY);
if (row) {
const persisted = (0, n8n_workflow_1.jsonParse)(row.value, {
fallbackValue: {},
});
this.applyAdminSettings(persisted);
}
}
getAdminSettings() {
const c = this.config;
return {
enabled: this.enabled,
lastMessages: c.lastMessages,
embedderModel: c.embedderModel,
semanticRecallTopK: c.semanticRecallTopK,
subAgentMaxSteps: c.subAgentMaxSteps,
browserMcp: c.browserMcp,
permissions: { ...this.permissions },
mcpServers: c.mcpServers,
sandboxEnabled: c.sandboxEnabled,
sandboxProvider: c.sandboxProvider,
sandboxImage: c.sandboxImage,
sandboxTimeout: c.sandboxTimeout,
daytonaCredentialId: this.adminDaytonaCredentialId,
n8nSandboxCredentialId: this.adminN8nSandboxCredentialId,
searchCredentialId: this.adminSearchCredentialId,
localGatewayDisabled: this.isLocalGatewayDisabled(),
};
}
async updateAdminSettings(update) {
if (this.isCloud) {
this.rejectManagedFields(update, InstanceAiSettingsService_1.CLOUD_MANAGED_ADMIN_FIELDS, 'cloud');
}
else if (this.aiService.isProxyEnabled()) {
this.rejectManagedFields(update, InstanceAiSettingsService_1.PROXY_MANAGED_ADMIN_FIELDS, 'proxy');
}
const c = this.config;
const previousMcpServers = c.mcpServers;
const previousBrowserMcp = c.browserMcp;
if (update.enabled !== undefined)
this.enabled = update.enabled;
if (update.lastMessages !== undefined)
c.lastMessages = update.lastMessages;
if (update.embedderModel !== undefined)
c.embedderModel = update.embedderModel;
if (update.semanticRecallTopK !== undefined)
c.semanticRecallTopK = update.semanticRecallTopK;
if (update.subAgentMaxSteps !== undefined)
c.subAgentMaxSteps = update.subAgentMaxSteps;
if (update.browserMcp !== undefined)
c.browserMcp = update.browserMcp;
if (update.permissions) {
this.permissions = { ...this.permissions, ...update.permissions };
}
if (update.mcpServers !== undefined)
c.mcpServers = update.mcpServers;
if (update.sandboxEnabled !== undefined)
c.sandboxEnabled = update.sandboxEnabled;
if (update.sandboxProvider !== undefined)
c.sandboxProvider = update.sandboxProvider;
if (update.sandboxImage !== undefined)
c.sandboxImage = update.sandboxImage;
if (update.sandboxTimeout !== undefined)
c.sandboxTimeout = update.sandboxTimeout;
if (update.daytonaCredentialId !== undefined)
this.adminDaytonaCredentialId = update.daytonaCredentialId;
if (update.n8nSandboxCredentialId !== undefined)
this.adminN8nSandboxCredentialId = update.n8nSandboxCredentialId;
if (update.searchCredentialId !== undefined)
this.adminSearchCredentialId = update.searchCredentialId;
if (update.localGatewayDisabled !== undefined)
c.localGatewayDisabled = update.localGatewayDisabled;
await this.persistAdminSettings();
this.eventService.emit('instance-ai-settings-updated', {
mcpSettingsChanged: c.mcpServers !== previousMcpServers || c.browserMcp !== previousBrowserMcp,
});
return this.getAdminSettings();
}
async getUserPreferences(user) {
const prefs = this.readUserPreferences(user);
const credentialId = prefs.credentialId ?? null;
let credentialType = null;
let credentialName = null;
if (credentialId) {
const cred = await this.credentialsFinderService.findCredentialForUser(credentialId, user, [
'credential:read',
]);
if (cred) {
credentialType = cred.type;
credentialName = cred.name;
}
}
return {
credentialId,
credentialType,
credentialName,
modelName: prefs.modelName || this.extractModelName(this.config.model),
localGatewayDisabled: prefs.localGatewayDisabled ?? false,
};
}
async updateUserPreferences(user, update) {
if (this.isCloud) {
this.rejectManagedFields(update, InstanceAiSettingsService_1.CLOUD_MANAGED_PREFERENCE_FIELDS, 'cloud');
}
else if (this.aiService.isProxyEnabled()) {
this.rejectManagedFields(update, InstanceAiSettingsService_1.PROXY_MANAGED_PREFERENCE_FIELDS, 'proxy');
}
const prefs = { ...this.readUserPreferences(user) };
if (update.credentialId !== undefined)
prefs.credentialId = update.credentialId;
if (update.modelName !== undefined)
prefs.modelName = update.modelName;
if (update.localGatewayDisabled !== undefined)
prefs.localGatewayDisabled = update.localGatewayDisabled;
await this.userService.updateSettings(user.id, { instanceAi: prefs });
user.settings = { ...(user.settings ?? {}), instanceAi: prefs };
return await this.getUserPreferences(user);
}
async listModelCredentials(user) {
if (this.aiService.isProxyEnabled())
return [];
const allCredentials = await this.credentialsFinderService.findCredentialsForUser(user, [
'credential:read',
]);
return allCredentials
.filter((c) => SUPPORTED_CREDENTIAL_TYPES.includes(c.type))
.map((c) => ({
id: c.id,
name: c.name,
type: c.type,
provider: CREDENTIAL_TO_MASTRA_PROVIDER[c.type] ?? 'custom',
}));
}
async listServiceCredentials(user) {
if (this.aiService.isProxyEnabled())
return [];
const allCredentials = await this.credentialsFinderService.findCredentialsForUser(user, [
'credential:read',
]);
return allCredentials
.filter((c) => SERVICE_CREDENTIAL_TYPES.includes(c.type))
.map((c) => ({
id: c.id,
name: c.name,
type: c.type,
provider: c.type,
}));
}
async resolveDaytonaConfig(user) {
const credentialId = this.adminDaytonaCredentialId;
if (!credentialId) {
const { daytonaApiUrl, daytonaApiKey } = this.config;
return {
apiUrl: daytonaApiUrl || undefined,
apiKey: daytonaApiKey || undefined,
};
}
const credential = await this.credentialsFinderService.findCredentialForUser(credentialId, user, ['credential:read']);
if (!credential) {
return {};
}
const data = await this.credentialsService.decrypt(credential, true);
return {
apiUrl: typeof data.apiUrl === 'string' ? data.apiUrl : undefined,
apiKey: typeof data.apiKey === 'string' ? data.apiKey : undefined,
};
}
async resolveN8nSandboxConfig(user) {
const { n8nSandboxServiceUrl, n8nSandboxServiceApiKey } = this.config;
const credentialId = this.adminN8nSandboxCredentialId;
if (!credentialId) {
return {
serviceUrl: n8nSandboxServiceUrl || undefined,
apiKey: n8nSandboxServiceApiKey || undefined,
};
}
const credential = await this.credentialsFinderService.findCredentialForUser(credentialId, user, ['credential:read']);
if (!credential) {
return {
serviceUrl: n8nSandboxServiceUrl || undefined,
apiKey: n8nSandboxServiceApiKey || undefined,
};
}
const data = await this.credentialsService.decrypt(credential, true);
const headerName = typeof data.name === 'string' ? data.name.trim().toLowerCase() : '';
const apiKey = typeof data.value === 'string' ? data.value : undefined;
return {
serviceUrl: n8nSandboxServiceUrl || undefined,
apiKey: headerName === 'x-api-key' ? apiKey : n8nSandboxServiceApiKey || undefined,
};
}
async resolveSearchConfig(user) {
const credentialId = this.adminSearchCredentialId;
if (!credentialId) {
const { braveSearchApiKey, searxngUrl } = this.config;
return {
braveApiKey: braveSearchApiKey || undefined,
searxngUrl: searxngUrl || undefined,
};
}
const credential = await this.credentialsFinderService.findCredentialForUser(credentialId, user, ['credential:read']);
if (!credential) {
return {};
}
const data = await this.credentialsService.decrypt(credential, true);
if (credential.type === 'braveSearchApi') {
return { braveApiKey: typeof data.apiKey === 'string' ? data.apiKey : undefined };
}
if (credential.type === 'searXngApi') {
return { searxngUrl: typeof data.apiUrl === 'string' ? data.apiUrl : undefined };
}
return {};
}
getPermissions() {
return { ...this.permissions };
}
async isLocalGatewayDisabledForUser(userId) {
if (!this.enabled)
return true;
if (this.config.localGatewayDisabled)
return true;
const user = await this.userRepository.findOneBy({ id: userId });
if (!user)
return true;
return this.readUserPreferences(user).localGatewayDisabled ?? false;
}
isAgentEnabled() {
return this.enabled;
}
isLocalGatewayDisabled() {
return this.config.localGatewayDisabled;
}
isInstanceAiEnabled() {
return this.enabled;
}
async resolveModelName(user) {
const prefs = this.readUserPreferences(user);
return prefs.modelName || this.extractModelName(this.config.model);
}
async resolveModelConfig(user) {
const prefs = this.readUserPreferences(user);
const credentialId = prefs.credentialId ?? null;
if (!credentialId) {
return this.envVarModelConfig();
}
const credential = await this.credentialsFinderService.findCredentialForUser(credentialId, user, ['credential:read']);
if (!credential) {
return this.envVarModelConfig();
}
const provider = CREDENTIAL_TO_MASTRA_PROVIDER[credential.type];
if (!provider) {
return this.envVarModelConfig();
}
const data = await this.credentialsService.decrypt(credential, true);
const apiKey = typeof data.apiKey === 'string' ? data.apiKey : '';
const urlField = URL_FIELD_MAP[credential.type];
const rawUrl = urlField ? data[urlField] : undefined;
const baseUrl = typeof rawUrl === 'string' ? rawUrl : '';
const modelName = prefs.modelName || this.extractModelName(this.config.model);
const id = `${provider}/${modelName}`;
if (baseUrl) {
return { id, url: baseUrl, ...(apiKey ? { apiKey } : {}) };
}
if (apiKey) {
return { id, url: '', apiKey };
}
return id;
}
rejectManagedFields(update, managedFields, label) {
const record = update;
const present = managedFields.filter((key) => key in record && record[key] !== undefined);
if (present.length > 0) {
throw new unprocessable_error_1.UnprocessableRequestError(`Cannot update ${label}-managed fields: ${present.join(', ')}`);
}
}
envVarModelConfig() {
const { model, modelUrl, modelApiKey } = this.config;
const id = model.includes('/')
? model
: `custom/${model}`;
if (modelUrl) {
return { id, url: modelUrl, ...(modelApiKey ? { apiKey: modelApiKey } : {}) };
}
if (modelApiKey) {
return { id, url: '', apiKey: modelApiKey };
}
return model;
}
extractModelName(model) {
const slash = model.indexOf('/');
return slash >= 0 ? model.slice(slash + 1) : model;
}
applyAdminSettings(persisted) {
const c = this.config;
if (persisted.enabled !== undefined)
this.enabled = persisted.enabled;
if (persisted.lastMessages !== undefined)
c.lastMessages = persisted.lastMessages;
if (persisted.embedderModel !== undefined)
c.embedderModel = persisted.embedderModel;
if (persisted.semanticRecallTopK !== undefined)
c.semanticRecallTopK = persisted.semanticRecallTopK;
if (persisted.subAgentMaxSteps !== undefined)
c.subAgentMaxSteps = persisted.subAgentMaxSteps;
if (persisted.browserMcp !== undefined)
c.browserMcp = persisted.browserMcp;
if (persisted.permissions) {
this.permissions = {
...api_types_1.DEFAULT_INSTANCE_AI_PERMISSIONS,
...persisted.permissions,
};
}
if (persisted.mcpServers !== undefined)
c.mcpServers = persisted.mcpServers;
if (persisted.sandboxEnabled !== undefined)
c.sandboxEnabled = persisted.sandboxEnabled;
if (persisted.sandboxProvider !== undefined)
c.sandboxProvider = persisted.sandboxProvider;
if (persisted.sandboxImage !== undefined)
c.sandboxImage = persisted.sandboxImage;
if (persisted.sandboxTimeout !== undefined)
c.sandboxTimeout = persisted.sandboxTimeout;
if (persisted.daytonaCredentialId !== undefined)
this.adminDaytonaCredentialId = persisted.daytonaCredentialId;
if (persisted.n8nSandboxCredentialId !== undefined)
this.adminN8nSandboxCredentialId = persisted.n8nSandboxCredentialId;
if (persisted.searchCredentialId !== undefined)
this.adminSearchCredentialId = persisted.searchCredentialId;
if (persisted.localGatewayDisabled !== undefined)
c.localGatewayDisabled = persisted.localGatewayDisabled;
}
readUserPreferences(user) {
return user.settings?.instanceAi ?? {};
}
async persistAdminSettings() {
const c = this.config;
const value = {
enabled: this.enabled,
lastMessages: c.lastMessages,
embedderModel: c.embedderModel,
semanticRecallTopK: c.semanticRecallTopK,
subAgentMaxSteps: c.subAgentMaxSteps,
browserMcp: c.browserMcp,
permissions: this.permissions,
mcpServers: c.mcpServers,
sandboxEnabled: c.sandboxEnabled,
sandboxProvider: c.sandboxProvider,
sandboxImage: c.sandboxImage,
sandboxTimeout: c.sandboxTimeout,
daytonaCredentialId: this.adminDaytonaCredentialId,
n8nSandboxCredentialId: this.adminN8nSandboxCredentialId,
searchCredentialId: this.adminSearchCredentialId,
localGatewayDisabled: c.localGatewayDisabled,
};
await this.settingsRepository.upsert({ key: ADMIN_SETTINGS_KEY, value: JSON.stringify(value), loadOnStartup: true }, ['key']);
}
};
exports.InstanceAiSettingsService = InstanceAiSettingsService;
InstanceAiSettingsService.PROXY_MANAGED_ADMIN_FIELDS = [
'sandboxEnabled',
'sandboxProvider',
'sandboxImage',
'sandboxTimeout',
'daytonaCredentialId',
'searchCredentialId',
];
InstanceAiSettingsService.PROXY_MANAGED_PREFERENCE_FIELDS = [
'credentialId',
'modelName',
];
InstanceAiSettingsService.CLOUD_MANAGED_ADMIN_FIELDS = [
...InstanceAiSettingsService_1.PROXY_MANAGED_ADMIN_FIELDS,
'n8nSandboxCredentialId',
'lastMessages',
'embedderModel',
'semanticRecallTopK',
'subAgentMaxSteps',
'browserMcp',
'mcpServers',
];
InstanceAiSettingsService.CLOUD_MANAGED_PREFERENCE_FIELDS = [
...InstanceAiSettingsService_1.PROXY_MANAGED_PREFERENCE_FIELDS,
];
exports.InstanceAiSettingsService = InstanceAiSettingsService = InstanceAiSettingsService_1 = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [config_1.GlobalConfig,
db_1.SettingsRepository,
db_1.UserRepository,
user_service_1.UserService,
ai_service_1.AiService,
credentials_service_1.CredentialsService,
credentials_finder_service_1.CredentialsFinderService,
event_service_1.EventService])
], InstanceAiSettingsService);
//# sourceMappingURL=instance-ai-settings.service.js.map