n8n
Version:
n8n Workflow Automation Tool
420 lines • 20.9 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
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 __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AgentsBuilderToolsService = void 0;
exports.getAgentConfigHash = getAgentConfigHash;
const agents_1 = require("@n8n/agents");
const api_types_1 = require("@n8n/api-types");
const db_1 = require("@n8n/db");
const di_1 = require("@n8n/di");
const node_crypto_1 = require("node:crypto");
const zod_1 = require("zod");
const agents_tools_service_1 = require("../agents-tools.service");
const agents_service_1 = require("../agents.service");
const agent_config_composition_1 = require("../json-config/agent-config-composition");
const agent_json_config_1 = require("../json-config/agent-json-config");
const agent_secure_runtime_1 = require("../runtime/agent-secure-runtime");
const builder_model_lookup_service_1 = require("./builder-model-lookup.service");
const interactive_1 = require("./interactive");
const builder_tool_names_1 = require("./builder-tool-names");
const EMPTY_INSTRUCTIONS_ERROR = {
path: '/instructions',
message: 'Refusing to write an agent with empty instructions. Ask the user what the agent should do before calling write_config or patch_config again.',
};
const STALE_CONFIG_ERROR = {
path: '(root)',
message: 'Agent config changed since you last read it. Call read_config and retry with the returned configHash.',
};
function rejectIfEmptyInstructions(config) {
if (!config.instructions.trim()) {
return { errors: [EMPTY_INSTRUCTIONS_ERROR] };
}
return null;
}
function isRecord(value) {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function canonicalizeJson(value) {
if (Array.isArray(value)) {
return value.map((item) => canonicalizeJson(item));
}
if (!isRecord(value))
return value;
const sorted = {};
for (const key of Object.keys(value).sort()) {
sorted[key] = canonicalizeJson(value[key]);
}
return sorted;
}
function getAgentConfigHash(config) {
if (!config)
return null;
return (0, node_crypto_1.createHash)('sha256')
.update(JSON.stringify(canonicalizeJson(config)))
.digest('hex');
}
function snapshotFromConfig(config, updatedAt, versionId) {
return {
config,
configHash: getAgentConfigHash(config),
updatedAt,
versionId,
};
}
let AgentsBuilderToolsService = class AgentsBuilderToolsService {
constructor(agentsService, secureRuntime, workflowRepository, agentsToolsService, builderModelLookupService) {
this.agentsService = agentsService;
this.secureRuntime = secureRuntime;
this.workflowRepository = workflowRepository;
this.agentsToolsService = agentsToolsService;
this.builderModelLookupService = builderModelLookupService;
}
getTools(agentId, projectId, credentialProvider, user) {
return {
json: this.getJsonTools(agentId, projectId, credentialProvider, user),
shared: this.getSharedTools(agentId, projectId, credentialProvider),
};
}
getJsonTools(agentId, projectId, credentialProvider, user) {
const readConfigTool = new agents_1.Tool(builder_tool_names_1.BUILDER_TOOLS.READ_CONFIG)
.description('Read the latest persisted agent configuration and freshness metadata. ' +
'Returns { ok: true, config, configHash, updatedAt, versionId }. ' +
'Call this before every write_config or patch_config and use configHash as baseConfigHash.')
.input(zod_1.z.object({}))
.handler(async () => {
try {
return { ok: true, ...(await this.getConfigSnapshot(agentId, projectId)) };
}
catch (e) {
return {
ok: false,
errors: [{ path: '(root)', message: e instanceof Error ? e.message : String(e) }],
};
}
})
.build();
const writeConfigTool = new agents_1.Tool(builder_tool_names_1.BUILDER_TOOLS.WRITE_CONFIG)
.description('Create or replace the agent configuration by writing a complete JSON string. ' +
'Requires baseConfigHash from the immediately preceding read_config result, or from a stale retry response. ' +
'Do not use a configHash copied from the prompt snapshot. ' +
'Returns { ok: true, config, configHash, updatedAt, versionId } on success or ' +
'{ ok: false, stage, errors } with path, message, expected, received fields on failure.')
.input(zod_1.z.object({
json: zod_1.z.string().describe('Complete agent configuration as a JSON string'),
baseConfigHash: zod_1.z
.string()
.nullable()
.describe('configHash from the immediately preceding read_config result; null only if no config exists'),
}))
.handler(async ({ json, baseConfigHash }) => {
const parsed = (0, agent_json_config_1.tryParseConfigJson)(json);
if (!parsed.ok) {
return { ok: false, errors: parsed.errors };
}
let snapshot;
try {
snapshot = await this.getConfigSnapshot(agentId, projectId);
}
catch (e) {
return {
ok: false,
stage: 'stale',
errors: [{ path: '(root)', message: e instanceof Error ? e.message : String(e) }],
};
}
if (baseConfigHash !== snapshot.configHash) {
return { ok: false, stage: 'stale', errors: [STALE_CONFIG_ERROR], ...snapshot };
}
const zodResult = agent_json_config_1.AgentJsonConfigSchema.safeParse(parsed.data);
if (!zodResult.success) {
return { ok: false, errors: (0, agent_json_config_1.formatZodErrors)(zodResult.error) };
}
const emptyInstructions = rejectIfEmptyInstructions(zodResult.data);
if (emptyInstructions) {
return { ok: false, errors: emptyInstructions.errors };
}
try {
const result = await this.agentsService.updateConfig(agentId, projectId, zodResult.data);
return {
ok: true,
...snapshotFromConfig(result.config, result.updatedAt, result.versionId),
};
}
catch (e) {
return {
ok: false,
stage: 'schema',
errors: [{ path: '(root)', message: e instanceof Error ? e.message : String(e) }],
};
}
})
.build();
const patchConfigTool = new agents_1.Tool(builder_tool_names_1.BUILDER_TOOLS.PATCH_CONFIG)
.description('Apply RFC 6902 JSON Patch operations to the current agent configuration. ' +
'Pass an array of patch operations as a JSON string. ' +
'Requires baseConfigHash from the immediately preceding read_config result, or from a stale retry response. ' +
'Do not use a configHash copied from the prompt snapshot. ' +
'Supported ops: add, remove, replace, move, copy, test. ' +
'Returns { ok: true, config, configHash, updatedAt, versionId } on success or ' +
'{ ok: false, stage, errors } on failure. ' +
'stage is "parse", "stale", "patch", or "schema".')
.input(zod_1.z.object({
operations: zod_1.z.string().describe('RFC 6902 JSON Patch operations array as a JSON string'),
baseConfigHash: zod_1.z
.string()
.nullable()
.describe('configHash from the immediately preceding read_config result; null only if no config exists'),
}))
.handler(async ({ operations, baseConfigHash, }) => {
const parsedOps = (0, agent_json_config_1.tryParseConfigJson)(operations);
if (!parsedOps.ok) {
return { ok: false, stage: 'parse', errors: parsedOps.errors };
}
let snapshot;
try {
snapshot = await this.getConfigSnapshot(agentId, projectId);
}
catch (e) {
return {
ok: false,
stage: 'stale',
errors: [{ path: '(root)', message: e instanceof Error ? e.message : String(e) }],
};
}
if (baseConfigHash !== snapshot.configHash) {
return { ok: false, stage: 'stale', errors: [STALE_CONFIG_ERROR], ...snapshot };
}
if (!snapshot.config) {
return {
ok: false,
stage: 'patch',
errors: [{ path: '(root)', message: 'Agent has no JSON config yet.' }],
};
}
const jsonpatch = (await Promise.resolve().then(() => __importStar(require('fast-json-patch')))).default;
const ops = parsedOps.data;
const patchError = jsonpatch.validate(ops, snapshot.config);
if (patchError) {
const opPath = patchError.operation?.path ?? '(root)';
return {
ok: false,
stage: 'patch',
errors: [{ path: opPath, message: patchError.message ?? 'Invalid patch operation' }],
};
}
const patched = jsonpatch.applyPatch(jsonpatch.deepClone(snapshot.config), ops)
.newDocument;
const zodResult = agent_json_config_1.AgentJsonConfigSchema.safeParse(patched);
if (!zodResult.success) {
return { ok: false, stage: 'schema', errors: (0, agent_json_config_1.formatZodErrors)(zodResult.error) };
}
const emptyInstructions = rejectIfEmptyInstructions(zodResult.data);
if (emptyInstructions) {
return { ok: false, stage: 'schema', errors: emptyInstructions.errors };
}
try {
const result = await this.agentsService.updateConfig(agentId, projectId, zodResult.data);
return {
ok: true,
...snapshotFromConfig(result.config, result.updatedAt, result.versionId),
};
}
catch (e) {
return {
ok: false,
stage: 'schema',
errors: [{ path: '(root)', message: e instanceof Error ? e.message : String(e) }],
};
}
})
.build();
const listIntegrationTypesTool = new agents_1.Tool(builder_tool_names_1.BUILDER_TOOLS.LIST_INTEGRATION_TYPES)
.description("List trigger / integration types that can be added to the agent's `integrations` array. " +
'Returns the schedule trigger plus every connected chat platform with the list of ' +
'credential types it supports (`credentialTypes: string[]`). ' +
'Call this BEFORE asking the user for a credential. Then pick ONE entry from the ' +
'returned `credentialTypes` (prefer the OAuth variant if present, e.g. `slackOAuth2Api` ' +
'over `slackApi`) and pass it to `ask_credential` as the singular `credentialType` arg.')
.input(zod_1.z.object({}))
.handler(async () => {
const chat = this.agentsService.listChatIntegrations();
return [
{ type: 'schedule', label: 'Schedule', icon: 'clock', credentialTypes: [] },
...chat,
];
})
.build();
const modelLookup = {
list: async (credentialId, credentialType, lookup) => await this.builderModelLookupService.list(user, credentialId, credentialType, lookup),
};
return [
readConfigTool,
writeConfigTool,
patchConfigTool,
listIntegrationTypesTool,
(0, interactive_1.buildResolveLlmTool)({ credentialProvider, modelLookup }),
(0, interactive_1.buildAskCredentialTool)({ credentialProvider }),
(0, interactive_1.buildAskLlmTool)(),
(0, interactive_1.buildAskQuestionTool)(),
];
}
getSharedTools(agentId, projectId, credentialProvider) {
const buildCustomToolTool = new agents_1.Tool(builder_tool_names_1.BUILDER_TOOLS.BUILD_CUSTOM_TOOL)
.description('Compile and store a custom tool. Pass the complete TypeScript source ' +
'using `export default new Tool(...)` builder chain. The code is validated in a ' +
'sandbox and saved against the agent, but this does NOT register the tool in the ' +
'agent config — follow up with patch_config (or write_config) to add a ' +
'`{ type: "custom", id }` entry to `tools` so the agent actually uses it. ' +
'Returns { ok: true, id, descriptor } or { ok: false, errors }.')
.input(zod_1.z.object({
code: zod_1.z
.string()
.describe('Complete TypeScript source using export default new Tool(...)'),
}))
.handler(async ({ code }) => {
try {
const descriptor = await this.secureRuntime.describeToolSecurely(code);
const built = await this.agentsService.buildCustomTool(agentId, projectId, code, descriptor);
return { ok: true, id: built.id, descriptor };
}
catch (e) {
return {
ok: false,
errors: [{ message: e instanceof Error ? e.message : String(e) }],
};
}
})
.build();
const createSkillTool = new agents_1.Tool(builder_tool_names_1.BUILDER_TOOLS.CREATE_SKILL)
.description('Create and store an agent skill. Pass the skill name, a short description, and the full skill body. ' +
'The description should help the runtime decide when to load it. ' +
'The body is stored as the skill instructions, but this does NOT attach the skill to the agent config. ' +
'Follow up with read_config and patch_config (or write_config) to add a `{ type: "skill", id }` entry to `skills`. ' +
'Returns { ok: true, id, skill } or { ok: false, errors }.')
.input(zod_1.z.object({
name: api_types_1.agentSkillSchema.shape.name.describe('Human-readable skill name'),
description: api_types_1.agentSkillSchema.shape.description.describe('Short description of when to load the skill.'),
body: api_types_1.agentSkillSchema.shape.instructions.describe('Full skill instructions/body'),
}))
.handler(async ({ name, description, body, }) => {
const skill = { name, description, instructions: body };
const validation = api_types_1.agentSkillSchema.safeParse(skill);
if (!validation.success) {
return { ok: false, errors: (0, agent_json_config_1.formatZodErrors)(validation.error) };
}
try {
const created = await this.agentsService.createSkill(agentId, projectId, skill);
return { ok: true, id: created.id, skill: created.skill };
}
catch (e) {
return {
ok: false,
errors: [{ message: e instanceof Error ? e.message : String(e) }],
};
}
})
.build();
const listWorkflowsTool = new agents_1.Tool('list_workflows')
.description('List the n8n workflows that can be attached as tools via `type: "workflow"` in the agent config. ' +
'ALWAYS call this at the start — workflows are the preferred way to give agents real capabilities ' +
'(sending emails, creating calendar events, querying databases, calling APIs, etc.). ' +
'Only returns workflows with supported trigger types.')
.input(zod_1.z.object({}))
.handler(async () => {
const workflows = await this.workflowRepository.find({
select: ['id', 'name', 'nodes', 'active', 'updatedAt'],
where: { shared: { projectId } },
relations: ['shared'],
order: { updatedAt: 'DESC' },
take: 100,
});
const SUPPORTED_TRIGGERS = {
'n8n-nodes-base.manualTrigger': 'manual',
'n8n-nodes-base.executeWorkflowTrigger': 'executeWorkflow',
'n8n-nodes-base.chatTrigger': 'chat',
'n8n-nodes-base.scheduleTrigger': 'schedule',
'n8n-nodes-base.formTrigger': 'form',
};
const compatible = workflows
.map((w) => {
const triggerNode = (w.nodes ?? []).find((n) => SUPPORTED_TRIGGERS[n.type]);
if (!triggerNode)
return null;
return {
name: w.name,
active: w.active,
triggerType: SUPPORTED_TRIGGERS[triggerNode.type],
};
})
.filter(Boolean);
return { workflows: compatible };
})
.build();
return [
buildCustomToolTool,
createSkillTool,
listWorkflowsTool,
...this.agentsToolsService.getSharedTools(credentialProvider, 'Read-only inspection of available credentials. Use ask_credential to let the user ' +
'pick the credential to wire into a node tool — never copy ids from this list directly ' +
'into the config.'),
];
}
async getConfigSnapshot(agentId, projectId) {
const agent = await this.agentsService.findById(agentId, projectId);
if (!agent)
throw new Error('Agent not found');
const config = (0, agent_config_composition_1.composeJsonConfig)(agent);
return snapshotFromConfig(config, agent.updatedAt.toISOString(), agent.versionId);
}
};
exports.AgentsBuilderToolsService = AgentsBuilderToolsService;
exports.AgentsBuilderToolsService = AgentsBuilderToolsService = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [agents_service_1.AgentsService,
agent_secure_runtime_1.AgentSecureRuntime,
db_1.WorkflowRepository,
agents_tools_service_1.AgentsToolsService,
builder_model_lookup_service_1.BuilderModelLookupService])
], AgentsBuilderToolsService);
//# sourceMappingURL=agents-builder-tools.service.js.map