n8n
Version:
n8n Workflow Automation Tool
209 lines (207 loc) • 8.92 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildFromJson = buildFromJson;
const agents_1 = require("@n8n/agents");
const zod_1 = require("zod");
const credential_field_mapping_1 = require("./credential-field-mapping");
const provider_tool_aliases_1 = require("./provider-tool-aliases");
const DEFAULT_WORKING_MEMORY_TEMPLATE = `# Thread memory
- User facts:
- User preferences/instructions:
- Current goal/task:
- Current state:
- Key active items:
- Decisions made:
- Open follow-ups:
- Resolved or superseded:`;
const DEFAULT_WORKING_MEMORY_INSTRUCTION = [
'You have thread-scoped working memory for this conversation.',
`When the user shares durable facts, preferences, decisions, goals, or unresolved follow-ups that will help later turns in this same thread, call ${agents_1.UPDATE_WORKING_MEMORY_TOOL_NAME} with the complete updated memory.`,
'Treat working memory as a current-state snapshot, not an append-only log.',
'Keep it concise, factual, and current.',
'When facts, preferences, priorities, goals, decisions, or statuses change, replace outdated active items with the latest state.',
'Preserve distinctions the user makes between primary, secondary, active, resolved, and superseded items.',
'Move resolved or superseded items to that section only when they will help later; otherwise remove them.',
'Preserve useful existing notes, remove stale or contradicted notes, and do not store secrets or one-off details.',
`Only call ${agents_1.UPDATE_WORKING_MEMORY_TOOL_NAME} when the memory should change.`,
].join(' ');
async function buildFromJson(config, toolDescriptors, options) {
const agent = new agents_1.Agent(config.name);
const slashIdx = config.model.indexOf('/');
const providerPrefix = slashIdx !== -1 ? config.model.slice(0, slashIdx) : '';
if (config.credential) {
const raw = await options.credentialProvider.resolve(config.credential);
const mapped = (0, credential_field_mapping_1.mapCredentialForProvider)(providerPrefix, raw);
agent.model({ id: config.model, ...mapped });
}
else {
agent.model(config.model);
}
const configuredSkills = getConfiguredSkills(config.skills ?? [], options.skills ?? {});
agent.instructions(withSkillCatalog(config.instructions, configuredSkills));
if (config.tools) {
for (const ref of config.tools) {
const built = await resolveToolRef(ref, toolDescriptors, options);
if (built) {
agent.tool(built);
}
}
}
if (configuredSkills.length > 0) {
agent.tool(createLoadSkillTool(configuredSkills));
}
if (config.providerTools) {
for (const [name, args] of Object.entries(config.providerTools)) {
const resolved = (0, provider_tool_aliases_1.resolveProviderToolName)(name);
agent.providerTool({ name: resolved, args });
}
}
if (config.memory?.enabled) {
await applyMemoryFromConfig(agent, config.memory, options.memoryFactory);
}
if (config.config) {
if (config.config.thinking) {
const { provider, ...rest } = config.config.thinking;
agent.thinking(provider, rest);
}
if (config.config.toolCallConcurrency) {
agent.toolCallConcurrency(config.config.toolCallConcurrency);
}
}
return agent;
}
function getConfiguredSkills(refs, skills) {
const seen = new Set();
const configured = [];
for (const ref of refs) {
if (seen.has(ref.id))
continue;
seen.add(ref.id);
const skill = skills[ref.id];
if (!skill)
throw new Error(`Skill "${ref.id}" not found in stored skill bodies`);
configured.push({ id: ref.id, skill });
}
return configured;
}
function withSkillCatalog(instructions, skills) {
if (skills.length === 0)
return instructions;
const catalog = formatSkillCatalog(skills);
const baseInstructions = instructions.trimEnd();
return `Skill loading protocol:
Skills are optional instruction packs, not execution tools. Use them to get extra guidance only when they are relevant to the user's current request.
Available skills:
${catalog}
When deciding whether to load a skill:
- Match the user's request against the skill name and description.
- If one skill clearly matches, call load_skill once with that skill's id, then follow the returned instructions.
- If the relevant skill was already loaded for this request, do not call load_skill again.
- If no skill clearly matches, do not call load_skill.
- Do not load a skill just because it is listed here.${baseInstructions ? `\n\n${baseInstructions}` : ''}`;
}
function createLoadSkillTool(skills) {
const skillsById = new Map(skills.map(({ id, skill }) => [id, skill]));
return new agents_1.Tool('load_skill')
.description('Load the full instructions for an attached skill. Use the skill id listed in the system instructions.')
.input(zod_1.z.object({
skillId: zod_1.z.string().describe('The skill id from the Available skills list'),
}))
.handler(async ({ skillId }) => {
const skill = skillsById.get(skillId);
if (!skill) {
return {
ok: false,
error: `Skill "${skillId}" is not attached to this agent.`,
};
}
return {
ok: true,
skillId,
name: skill.name,
description: skill.description,
instructions: skill.instructions,
};
})
.build();
}
function formatSkillCatalog(skills) {
return skills
.map(({ id, skill }) => `- name: ${skill.name}\n description: ${skill.description}\n id: ${id}`)
.join('\n');
}
async function resolveToolRef(ref, descriptors, options) {
switch (ref.type) {
case 'custom': {
const descriptor = descriptors[ref.id];
if (!descriptor) {
throw new Error(`Custom tool "${ref.id}" not found in tool descriptors`);
}
const builtTool = {
name: descriptor.name,
description: descriptor.description,
systemInstruction: descriptor.systemInstruction ?? undefined,
inputSchema: descriptor.inputSchema ?? undefined,
handler: async (input, ctx) => {
return await options.toolExecutor.executeTool(descriptor.name, input, {
resumeData: 'resumeData' in ctx ? ctx.resumeData : undefined,
parentTelemetry: ctx.parentTelemetry,
});
},
providerOptions: descriptor.providerOptions,
};
if (ref.requireApproval) {
return (0, agents_1.wrapToolForApproval)(builtTool, { requireApproval: true });
}
return builtTool;
}
case 'workflow': {
const marker = {
name: ref.name ?? ref.workflow,
description: ref.description ?? `Execute the "${ref.workflow}" workflow`,
editable: false,
metadata: {
workflowTool: true,
workflowName: ref.workflow,
options: { name: ref.name, description: ref.description },
},
};
const tool = (await options.resolveTool?.(ref)) ?? marker;
if (ref.requireApproval) {
return (0, agents_1.wrapToolForApproval)(tool, { requireApproval: true });
}
return tool;
}
case 'node': {
const marker = {
name: ref.name,
description: ref.description ?? `Execute node ${ref.name}`,
editable: false,
metadata: { nodeTool: true, ...ref.node },
};
const tool = (await options.resolveTool?.(ref)) ?? marker;
if (ref.requireApproval) {
return (0, agents_1.wrapToolForApproval)(tool, { requireApproval: true });
}
return tool;
}
}
}
async function applyMemoryFromConfig(agent, memoryConfig, memoryFactory) {
const memory = new agents_1.Memory();
const builtMemory = memoryFactory(memoryConfig);
memory.storage(await Promise.resolve(builtMemory));
memory
.freeform(DEFAULT_WORKING_MEMORY_TEMPLATE)
.scope('thread')
.instruction(DEFAULT_WORKING_MEMORY_INSTRUCTION);
if (memoryConfig.lastMessages) {
memory.lastMessages(memoryConfig.lastMessages);
}
if (memoryConfig.semanticRecall) {
memory.semanticRecall(memoryConfig.semanticRecall);
}
memory.titleGeneration({ sync: true });
agent.memory(memory);
}
//# sourceMappingURL=from-json-config.js.map