UNPKG

n8n

Version:

n8n Workflow Automation Tool

247 lines 11.1 kB
"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 AiGatewayService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.AiGatewayService = void 0; const backend_common_1 = require("@n8n/backend-common"); const config_1 = require("@n8n/config"); const constants_1 = require("@n8n/constants"); const di_1 = require("@n8n/di"); const db_1 = require("@n8n/db"); const n8n_core_1 = require("n8n-core"); const n8n_workflow_1 = require("n8n-workflow"); const constants_2 = require("../constants"); const feature_not_licensed_error_1 = require("../errors/feature-not-licensed.error"); const license_1 = require("../license"); const ownership_service_1 = require("../services/ownership.service"); const url_service_1 = require("../services/url.service"); let AiGatewayService = AiGatewayService_1 = class AiGatewayService { constructor(globalConfig, license, licenseState, instanceSettings, ownershipService, userRepository, urlService) { this.globalConfig = globalConfig; this.license = license; this.licenseState = licenseState; this.instanceSettings = instanceSettings; this.ownershipService = ownershipService; this.userRepository = userRepository; this.urlService = urlService; this.tokenCache = new Map(); this.TOKEN_CACHE_MAX_SIZE = 500; this.pendingTokenRequests = new Map(); this.gatewayConfig = null; this.configFetchedAt = 0; } async resolveUserId({ userId, projectId, workflowId, }) { if (userId) return userId; const resolvedProjectId = projectId ?? (workflowId ? (await this.ownershipService.getWorkflowProjectCached(workflowId))?.id : undefined); const owner = resolvedProjectId ? await this.ownershipService.getPersonalProjectOwnerCached(resolvedProjectId) : null; if (owner) return owner.id; try { return (await this.ownershipService.getInstanceOwner()).id; } catch { return undefined; } } async getSyntheticCredential({ credentialType, userId, workflowId, projectId, executionId, }) { if (!this.licenseState.isAiGatewayLicensed()) { throw new feature_not_licensed_error_1.FeatureNotLicensedError(constants_1.LICENSE_FEATURES.AI_GATEWAY); } const baseUrl = this.requireBaseUrl(); const config = await this.getGatewayConfig(); const providerConfig = config.providerConfig[credentialType]; if (!providerConfig) { throw new n8n_workflow_1.UserError(`Credential type "${credentialType}" is not supported by AI Gateway.`); } const resolvedUserId = await this.resolveUserId({ userId, projectId, workflowId }); if (!resolvedUserId) { throw new n8n_workflow_1.UserError('Failed to resolve user for AI Gateway attribution.'); } const jwt = await this.getOrFetchToken(resolvedUserId); if (!jwt) { throw new n8n_workflow_1.UserError('Failed to obtain a valid AI Gateway token.'); } const gatewayUrl = this.buildGatewayUrl(baseUrl, providerConfig.gatewayPath, { executionId, workflowId, }); return { [providerConfig.apiKeyField]: jwt, [providerConfig.urlField]: gatewayUrl, }; } async getUsage(userId, offset, limit) { const baseUrl = this.requireBaseUrl(); const jwt = await this.getOrFetchToken(userId); if (!jwt) { throw new n8n_workflow_1.UserError('Failed to obtain a valid AI Gateway token.'); } const url = new URL(`${baseUrl}/v1/gateway/usage`); url.searchParams.set('offset', String(offset)); url.searchParams.set('limit', String(limit)); const response = await fetch(url.toString(), { method: 'GET', headers: { Authorization: `Bearer ${jwt}` }, }); if (!response.ok) { throw new n8n_workflow_1.UserError(`Failed to fetch AI Gateway usage: HTTP ${response.status}`); } const data = (await response.json()); if (!Array.isArray(data.entries) || typeof data.total !== 'number') { throw new n8n_workflow_1.UserError('AI Gateway returned an invalid usage response.'); } return data; } async getWallet(userId) { const baseUrl = this.requireBaseUrl(); const jwt = await this.getOrFetchToken(userId); if (!jwt) { throw new n8n_workflow_1.UserError('Failed to obtain a valid AI Gateway token.'); } const response = await fetch(`${baseUrl}/v1/gateway/wallet`, { method: 'GET', headers: { Authorization: `Bearer ${jwt}` }, }); if (!response.ok) { throw new n8n_workflow_1.UserError(`Failed to fetch AI Gateway wallet: HTTP ${response.status}`); } return this.parseWalletResponse(await response.json()); } parseWalletResponse(data) { const d = data; if (typeof d.budget !== 'number' || typeof d.balance !== 'number') { throw new n8n_workflow_1.UserError('AI Gateway returned an invalid wallet response.'); } return d; } buildGatewayUrl(baseUrl, gatewayPath, context) { if (context.executionId && context.workflowId) { if (!gatewayPath.startsWith(AiGatewayService_1.GATEWAY_PATH_PREFIX)) { return `${baseUrl}${gatewayPath}`; } const providerSuffix = gatewayPath.slice(AiGatewayService_1.GATEWAY_PATH_PREFIX.length); return `${baseUrl}${AiGatewayService_1.GATEWAY_PATH_PREFIX}/exec/${encodeURIComponent(context.executionId)}/${encodeURIComponent(context.workflowId)}${providerSuffix}`; } return `${baseUrl}${gatewayPath}`; } requireBaseUrl() { const url = this.globalConfig.aiAssistant.baseUrl; if (!url) throw new n8n_workflow_1.UserError('AI Gateway is not configured. Set the AI assistant base URL.'); return url; } isConfigStale() { return (this.gatewayConfig === null || Date.now() - this.configFetchedAt > AiGatewayService_1.CONFIG_TTL_MS); } async getGatewayConfig() { if (!this.isConfigStale()) return this.gatewayConfig; const baseUrl = this.requireBaseUrl(); const response = await fetch(`${baseUrl}/v1/gateway/config`); if (!response.ok) { throw new n8n_workflow_1.UserError(`Failed to fetch AI Gateway config: HTTP ${response.status}`); } const data = (await response.json()); if (!Array.isArray(data.nodes) || !Array.isArray(data.credentialTypes) || typeof data.providerConfig !== 'object') { throw new n8n_workflow_1.UserError('AI Gateway returned an invalid config response.'); } this.gatewayConfig = data; this.configFetchedAt = Date.now(); return data; } buildGatewayCredentialsHeaders(userId) { const headers = {}; headers['Content-Type'] = 'application/json'; headers['x-user-id'] = userId; headers['x-consumer-id'] = this.license.getConsumerId(); headers['x-sdk-version'] = constants_2.AI_ASSISTANT_SDK_VERSION; headers['x-n8n-version'] = constants_2.N8N_VERSION; headers['x-instance-id'] = this.instanceSettings.instanceId; return headers; } async getOrFetchToken(userId) { const key = `${this.instanceSettings.instanceId}:${userId}`; const cached = this.tokenCache.get(key); if (cached && cached.refreshAt > Date.now()) { return cached.token; } const pending = this.pendingTokenRequests.get(key); if (pending) return await pending; const promise = this.fetchAndCacheToken(userId, key); this.pendingTokenRequests.set(key, promise); try { return await promise; } finally { this.pendingTokenRequests.delete(key); } } async fetchAndCacheToken(userId, key) { const baseUrl = this.requireBaseUrl(); const [licenseCert, user] = await Promise.all([ this.license.loadCertStr(), this.userRepository.findOneBy({ id: userId }), ]); const response = await fetch(`${baseUrl}/v1/gateway/credentials`, { method: 'POST', headers: this.buildGatewayCredentialsHeaders(userId), body: JSON.stringify({ licenseCert, ...(user?.email && { userEmail: user.email }), ...(user && { userName: [user.firstName, user.lastName].filter(Boolean).join(' ') || undefined, }), instanceUrl: this.urlService.getInstanceBaseUrl(), }), }); if (!response.ok) { throw new n8n_workflow_1.UserError(`Failed to fetch AI Gateway token: HTTP ${response.status}`); } const { token, expiresIn } = (await response.json()); if (!token || typeof expiresIn !== 'number') { throw new n8n_workflow_1.UserError('AI Gateway returned an invalid token response.'); } if (this.tokenCache.size >= this.TOKEN_CACHE_MAX_SIZE) { this.tokenCache.delete(this.tokenCache.keys().next().value); } const lifetimeMs = expiresIn * 1000; this.tokenCache.set(key, { token, expiresAt: Date.now() + lifetimeMs, refreshAt: Date.now() + lifetimeMs * 0.9, }); return token; } }; exports.AiGatewayService = AiGatewayService; AiGatewayService.CONFIG_TTL_MS = 60 * 60 * 1000; AiGatewayService.GATEWAY_PATH_PREFIX = '/v1/gateway'; exports.AiGatewayService = AiGatewayService = AiGatewayService_1 = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [config_1.GlobalConfig, license_1.License, backend_common_1.LicenseState, n8n_core_1.InstanceSettings, ownership_service_1.OwnershipService, db_1.UserRepository, url_service_1.UrlService]) ], AiGatewayService); //# sourceMappingURL=ai-gateway.service.js.map