workflow-factor-os-simulator / apps /simulator /src /agents /credentialIntelligence.ts
PYAE1994's picture
Upload folder using huggingface_hub
dd480ef verified
/**
* Credential Intelligence System β€” UPGRADED
* Provider-agnostic, uses Node Registry for credential mapping
* Inspects workflow to identify required credentials
* Scans existing n8n credentials and maps types
* Detects missing credentials and blocks activation if needed
*/
import type {
N8nWorkflow,
CredentialAnalysis,
RequiredCredential,
AvailableCredential,
MissingCredential,
} from '../types/workflow';
import { N8N_NODE_REGISTRY } from '../knowledge/nodeRegistry';
// Extended node β†’ credential map (derived from registry + additional mappings)
const NODE_CREDENTIAL_MAP: Record<string, string | undefined> = {
'n8n-nodes-base.telegram': 'telegramApi',
'n8n-nodes-base.telegramTrigger': 'telegramApi',
'n8n-nodes-base.openAi': 'openAiApi',
'@n8n/n8n-nodes-langchain.openAi': 'openAiApi',
'@n8n/n8n-nodes-langchain.agent': undefined, // depends on sub-node
'@n8n/n8n-nodes-langchain.lmChatAnthropic': 'anthropicApi',
'@n8n/n8n-nodes-langchain.memoryBufferWindow': undefined,
'@n8n/n8n-nodes-langchain.toolCode': undefined,
'n8n-nodes-base.gmail': 'googleOAuth2Api',
'n8n-nodes-base.emailReadImap': 'imap',
'n8n-nodes-base.googleSheets': 'googleSheetsOAuth2Api',
'n8n-nodes-base.slack': 'slackApi',
'n8n-nodes-base.slackTrigger': 'slackOAuth2Api',
'n8n-nodes-base.airtable': 'airtableTokenApi',
'n8n-nodes-base.notion': 'notionApi',
'n8n-nodes-base.notionTrigger': 'notionApi',
'n8n-nodes-base.github': 'githubApi',
'n8n-nodes-base.githubTrigger': 'githubApi',
'n8n-nodes-base.stripe': 'stripeApi',
'n8n-nodes-base.hubspot': 'hubspotApi',
'n8n-nodes-base.postgres': 'postgres',
'n8n-nodes-base.mysql': 'mySql',
'n8n-nodes-base.mongodb': 'mongoDb',
'n8n-nodes-base.redis': 'redis',
// No credential needed
'n8n-nodes-base.httpRequest': undefined,
'n8n-nodes-base.webhook': undefined,
'n8n-nodes-base.scheduleTrigger': undefined,
'n8n-nodes-base.manualTrigger': undefined,
'n8n-nodes-base.code': undefined,
'n8n-nodes-base.set': undefined,
'n8n-nodes-base.if': undefined,
'n8n-nodes-base.switch': undefined,
'n8n-nodes-base.merge': undefined,
'n8n-nodes-base.splitInBatches': undefined,
'n8n-nodes-base.itemLists': undefined,
'n8n-nodes-base.filter': undefined,
'n8n-nodes-base.noOp': undefined,
'n8n-nodes-base.stopAndError': undefined,
'n8n-nodes-base.wait': undefined,
'n8n-nodes-base.respondToWebhook': undefined,
'n8n-nodes-base.stickyNote': undefined,
};
const CREDENTIAL_SETUP_INSTRUCTIONS: Record<string, string> = {
telegramApi: 'Create a Telegram Bot via @BotFather, copy the bot token, then add "Telegram API" credential in n8n Settings β†’ Credentials.',
openAiApi: 'Generate an API key at https://platform.openai.com/api-keys, then add "OpenAI" credential in n8n.',
anthropicApi: 'Get your API key at https://console.anthropic.com/, then add "Anthropic" credential in n8n.',
googleOAuth2Api: 'Set up a Google Cloud project, enable Gmail API, create OAuth2 credentials, then add "Google OAuth2 API" in n8n.',
googleSheetsOAuth2Api: 'Set up Google Cloud project, enable Sheets API, create OAuth2 credentials, then add "Google Sheets OAuth2 API" in n8n.',
slackApi: 'Create a Slack App at https://api.slack.com/apps with required scopes, then add "Slack API" credential in n8n.',
slackOAuth2Api: 'Create a Slack App with OAuth2, then add "Slack OAuth2 API" credential in n8n.',
airtableTokenApi: 'Generate a Personal Access Token at https://airtable.com/account, then add "Airtable Token API" in n8n.',
notionApi: 'Create an integration at https://www.notion.so/my-integrations, then add "Notion API" credential in n8n.',
githubApi: 'Generate a Personal Access Token at https://github.com/settings/tokens, then add "GitHub API" in n8n.',
stripeApi: 'Get your API key from https://dashboard.stripe.com/apikeys, then add "Stripe API" in n8n.',
hubspotApi: 'Create a Private App in HubSpot settings, copy the access token, then add "HubSpot API" in n8n.',
postgres: 'Provide your PostgreSQL host, port, database, username, and password in "Postgres" credential in n8n.',
mySql: 'Provide your MySQL host, port, database, username, and password in "MySQL" credential in n8n.',
mongoDb: 'Provide your MongoDB connection string in "MongoDB" credential in n8n.',
redis: 'Provide your Redis host, port, and optional password in "Redis" credential in n8n.',
imap: 'Provide your IMAP server host, port, username, and password in "IMAP" credential in n8n.',
};
export class CredentialIntelligence {
private n8nBaseUrl: string;
private n8nApiKey: string;
constructor(n8nBaseUrl: string, n8nApiKey: string) {
this.n8nBaseUrl = n8nBaseUrl;
this.n8nApiKey = n8nApiKey;
}
async analyse(jobId: string, workflow: N8nWorkflow): Promise<CredentialAnalysis> {
// ─── 1. Identify required credentials from workflow nodes ──────────────
const required: RequiredCredential[] = [];
const requiredTypes = new Set<string>();
workflow.nodes.forEach((node) => {
// Check registry-based credential (more accurate)
const registryDef = N8N_NODE_REGISTRY[node.type];
const credType = registryDef?.credentialType ?? NODE_CREDENTIAL_MAP[node.type];
if (credType !== undefined && credType !== '') {
if (!requiredTypes.has(credType)) {
required.push({
nodeId: node.id,
nodeName: node.name,
credentialType: credType,
credentialDisplayName: registryDef?.credentialDisplayName,
required: true,
});
requiredTypes.add(credType);
}
}
// Also check node.credentials field (explicitly set by compiler)
if (node.credentials) {
Object.keys(node.credentials).forEach((cType) => {
if (!requiredTypes.has(cType)) {
required.push({
nodeId: node.id,
nodeName: node.name,
credentialType: cType,
required: true,
});
requiredTypes.add(cType);
}
});
}
});
// ─── 2. Fetch available credentials from n8n ───────────────────────────
let available: AvailableCredential[] = [];
let n8nReachable = false;
try {
const resp = await fetch(`${this.n8nBaseUrl}/api/v1/credentials`, {
headers: { 'X-N8N-API-KEY': this.n8nApiKey },
signal: AbortSignal.timeout(8000),
});
if (resp.ok) {
const data = await resp.json() as { data: AvailableCredential[] };
available = data.data ?? [];
n8nReachable = true;
}
} catch {
// n8n not accessible β€” non-fatal, report as warning
}
const availableTypes = new Set(available.map((c) => c.type));
// ─── 3. Identify missing credentials ──────────────────────────────────
const missingTypeMap = new Map<string, string[]>();
required.forEach((req) => {
// Only report as missing if n8n is reachable and credential is not there
if (n8nReachable && !availableTypes.has(req.credentialType)) {
const existing = missingTypeMap.get(req.credentialType) ?? [];
if (!existing.includes(req.nodeName)) existing.push(req.nodeName);
missingTypeMap.set(req.credentialType, existing);
}
});
const missing: MissingCredential[] = [...missingTypeMap.entries()].map(([credType, nodeNames]) => ({
credentialType: credType,
requiredByNodes: nodeNames,
setupInstructions: CREDENTIAL_SETUP_INSTRUCTIONS[credType] ?? `Set up "${credType}" credential in n8n Settings β†’ Credentials.`,
}));
return {
jobId,
allCredentialsPresent: missing.length === 0,
n8nReachable,
required,
available,
missing,
analysedAt: new Date().toISOString(),
};
}
}