n8n
Version:
n8n Workflow Automation Tool
118 lines • 6.09 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildAskCredentialTool = buildAskCredentialTool;
exports.buildAskEmbeddingCredentialTool = buildAskEmbeddingCredentialTool;
const tool_1 = require("@n8n/agents/tool");
const api_types_1 = require("@n8n/api-types");
const telemetry_1 = require("@n8n/telemetry");
const nanoid_1 = require("nanoid");
function withNodeCredentialMap(input, credentialId, credentialName) {
const credentialSlot = input.credentialSlot ?? input.credentialType;
return {
credentialId,
credentialName,
credentials: {
[credentialSlot]: { id: credentialId, name: credentialName },
},
};
}
async function listExistingCredentials(credentialProvider, credentialType) {
const all = await credentialProvider.list();
return all.filter((c) => c.type === credentialType).map((c) => ({ id: c.id, name: c.name }));
}
async function resolveResume(input, resumeData, credentialProvider, track) {
if (!('credentials' in resumeData)) {
track(telemetry_1.TELEMETRY_EVENT.AGENTS.USER_PROVIDED_CREDENTIAL, {
credential_type: input.credentialType,
outcome: 'skipped',
});
return { skipped: true };
}
const credentialId = resumeData.credentials[input.credentialType];
if (!credentialId) {
track(telemetry_1.TELEMETRY_EVENT.AGENTS.USER_PROVIDED_CREDENTIAL, {
credential_type: input.credentialType,
outcome: 'skipped',
});
return { skipped: true };
}
const existingCredentials = await listExistingCredentials(credentialProvider, input.credentialType);
const match = existingCredentials.find((c) => c.id === credentialId);
track(telemetry_1.TELEMETRY_EVENT.AGENTS.USER_PROVIDED_CREDENTIAL, {
credential_type: input.credentialType,
outcome: 'provided',
});
return withNodeCredentialMap(input, credentialId, match?.name ?? credentialId);
}
async function resolveCredentialSelection(input, ctx, deps) {
if (ctx.resumeData !== undefined && ctx.resumeData !== null) {
return await resolveResume(input, ctx.resumeData, deps.credentialProvider, deps.track);
}
if (deps.isCredentialTypeKnown && !deps.isCredentialTypeKnown(input.credentialType)) {
throw new Error(`Unknown credential type "${input.credentialType}". Use an exact n8n credential type name.`);
}
const existingCredentials = await listExistingCredentials(deps.credentialProvider, input.credentialType);
const integrationCredentialIds = (await deps.listIntegrationCredentialIds?.()) ?? [];
const channelCredential = existingCredentials.find((credential) => integrationCredentialIds.includes(credential.id));
if (channelCredential) {
return withNodeCredentialMap(input, channelCredential.id, channelCredential.name);
}
if (existingCredentials.length === 1) {
return withNodeCredentialMap(input, existingCredentials[0].id, existingCredentials[0].name);
}
deps.track(telemetry_1.TELEMETRY_EVENT.AGENTS.BUILDER_REQUESTED_CREDENTIAL, {
credential_type: input.credentialType,
});
return await ctx.suspend({
requestId: (0, nanoid_1.nanoid)(),
message: input.purpose,
severity: 'info',
credentialRequests: [
{
credentialType: input.credentialType,
reason: input.purpose,
existingCredentials,
},
],
credentialFlow: { stage: 'generic' },
});
}
function buildAskCredentialTool(deps) {
return new tool_1.Tool(api_types_1.ASK_CREDENTIAL_TOOL_NAME)
.description('Show a credential picker card in the chat UI and suspend until the user selects ' +
'a credential. Call ONCE per credential slot. For an addition to an existing agent, ' +
'call it before the write_config / patch_config that introduces the tool. Never call ' +
'this during an initial build — follow the Initial Build rules in your system prompt; ' +
'use it for additions to an existing agent and follow-up setup turns. ' +
'Returns { credentialId, credentialName, credentials } on success ' +
'or { skipped: true } if the user skips credential setup so the tool can be added ' +
'without credentials. For node tools, copy the returned `credentials` object into `node.credentials`. Auto-resolves without ' +
'rendering a card when the agent has a chat channel configured whose credential matches the ' +
'requested type (the channel credential is reused so tools act through the same connection), ' +
'or when the user has exactly one credential of the requested type.')
.input(api_types_1.askCredentialInputSchema)
.suspend(api_types_1.credentialSuspendPayloadSchema)
.resume(api_types_1.credentialResumeSchema)
.handler(async (input, ctx) => {
return await resolveCredentialSelection(input, ctx, deps);
})
.build();
}
function buildAskEmbeddingCredentialTool(deps) {
return new tool_1.Tool(api_types_1.ASK_EMBEDDING_CREDENTIAL_TOOL_NAME)
.description('Resolve the OpenAI embedding credential for Episodic Memory. Tries to resolve n8n managed credential. Otherwise behaves ' +
'like ask_credential: show a credential picker card in the chat UI and suspend until ' +
'the user selects a credential. Returns { credentialId, credentialName, credentials } ' +
'on success or { skipped: true } if the user skips credential setup.')
.input(api_types_1.askCredentialInputSchema)
.suspend(api_types_1.credentialSuspendPayloadSchema)
.resume(api_types_1.credentialResumeSchema)
.handler(async (input, ctx) => {
if (deps.isAssistantProxyEnabled()) {
return withNodeCredentialMap(input, api_types_1.MANAGED_CREDENTIAL_TOKEN, 'Managed by n8n');
}
return await resolveCredentialSelection(input, ctx, deps);
})
.build();
}
//# sourceMappingURL=ask-credential.tool.js.map