n8n
Version:
n8n Workflow Automation Tool
859 lines • 49 kB
JavaScript
"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);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AgentsBuilderToolsService = void 0;
const agents_1 = require("@n8n/agents");
const tool_1 = require("@n8n/agents/tool");
const agent_config_1 = require("@n8n/ai-utilities/agent-config");
const api_types_1 = require("@n8n/api-types");
const backend_network_1 = require("@n8n/backend-network");
const config_1 = require("@n8n/config");
const di_1 = require("@n8n/di");
const zod_1 = require("zod");
const credential_types_1 = require("../../../credential-types");
const mcp_registry_service_1 = require("../../../modules/mcp-registry/registry/mcp-registry.service");
const node_types_1 = require("../../../node-types");
const oauth_service_1 = require("../../../oauth/oauth.service");
const check_access_1 = require("../../../permissions.ee/check-access");
const ai_gateway_service_1 = require("../../../services/ai-gateway.service");
const ai_service_1 = require("../../../services/ai.service");
const dynamic_node_parameters_service_1 = require("../../../services/dynamic-node-parameters.service");
const free_ai_credits_service_1 = require("../../../services/free-ai-credits.service");
const telemetry_1 = require("../../../telemetry");
const ai_proxy_fetch_1 = require("../../../utils/ai-proxy-fetch");
const agent_config_service_1 = require("../agent-config.service");
const agent_custom_tools_service_1 = require("../agent-custom-tools.service");
const agent_integration_persistence_service_1 = require("../agent-integration-persistence.service");
const agent_publish_service_1 = require("../agent-publish.service");
const agent_skills_service_1 = require("../agent-skills.service");
const agent_task_service_1 = require("../agent-task.service");
const agent_test_run_service_1 = require("../agent-test-run.service");
const agents_tools_service_1 = require("../agents-tools.service");
const agents_service_1 = require("../agents.service");
const attachable_workflows_service_1 = require("../attachable-workflows.service");
const agent_builder_preview_path_1 = require("./agent-builder-preview-path");
const builder_model_live_lookup_service_1 = require("./builder-model-live-lookup.service");
const builder_tool_names_1 = require("./builder-tool-names");
const get_resource_locator_options_tool_1 = require("./get-resource-locator-options.tool");
const interactive_1 = require("./interactive");
const resolve_integration_tool_1 = require("./resolve-integration.tool");
const search_mcp_servers_tool_1 = require("./search-mcp-servers.tool");
const skill_body_template_1 = require("./skill-body-template");
const task_objective_template_1 = require("./task-objective-template");
const verify_mcp_server_tool_1 = require("./verify-mcp-server.tool");
const agent_config_composition_1 = require("../json-config/agent-config-composition");
const reconcile_node_tool_gateway_credentials_1 = require("../json-config/reconcile-node-tool-gateway-credentials");
const agent_secure_runtime_1 = require("../runtime/agent-secure-runtime");
const agent_config_hash_1 = require("../utils/agent-config-hash");
const STALE_CONFIG_ERROR = {
path: '(root)',
message: 'Agent config changed since you last read it. Call read_config, then retry using the config and configHash it returns.',
};
const CLI_AGENT_CONFIG_MESSAGES = {
emptyInstructionsFollowUp: 'saving the config again.',
dynamicSelectorFollowUp: 'Load skill agent-builder-resource-locators, resolve a credential if missing, then call ' +
'get_resource_locator_options and write the returned parameterValue into nodeParameters.',
};
const createSkillInputSchema = zod_1.z
.object({
name: api_types_1.agentSkillSchema.shape.name.describe('Human-readable skill name'),
description: api_types_1.agentSkillSchema.shape.description.describe(skill_body_template_1.SKILL_DESCRIPTION_RULE),
instructions: api_types_1.agentSkillSchema.shape.instructions.describe(skill_body_template_1.SKILL_BODY_GUIDANCE),
allowedTools: api_types_1.agentSkillSchema.shape.allowedTools
.optional()
.describe('Exact target-agent tool names this skill is allowed to use.'),
references: api_types_1.agentSkillSchema.shape.references
.optional()
.describe('Markdown-only supporting files under references/... paths. References are not automatically loaded; instructions must say exactly when to load each reference by path.'),
})
.strict();
function snapshotFromConfig(config) {
return {
config,
configHash: (0, agent_config_hash_1.getAgentConfigHash)(config),
};
}
function parseBuilderWriteConfig(incoming, currentConfig) {
const sanitized = (0, api_types_1.sanitizeAgentJsonConfig)(incoming);
if (!(0, api_types_1.isDraftAgentConfig)(currentConfig) &&
(0, api_types_1.isDraftAgentConfig)(sanitized)) {
return {
success: false,
error: new zod_1.z.ZodError([
{
code: zod_1.z.ZodIssueCode.custom,
path: ['model'],
message: 'Model cannot be cleared once set',
},
]),
};
}
return api_types_1.AgentJsonConfigSchema.safeParse(sanitized);
}
function applyPromptCachingBuilderDefaults(config) {
const providerPrefix = (0, agent_config_1.getProviderPrefix)(config.model);
const capability = api_types_1.PROVIDER_CAPABILITIES[providerPrefix]?.promptCaching ?? false;
const resolved = (0, api_types_1.resolvePromptCaching)(config.config?.promptCaching, capability);
if (!resolved) {
if (!config.config || !('promptCaching' in config.config))
return config;
const { promptCaching: _promptCaching, ...restConfig } = config.config;
const { config: _config, ...restAgentConfig } = config;
return {
...restAgentConfig,
...(Object.keys(restConfig).length > 0 ? { config: restConfig } : {}),
};
}
return {
...config,
config: {
...(config.config ?? {}),
promptCaching: resolved,
},
};
}
let AgentsBuilderToolsService = class AgentsBuilderToolsService {
constructor(agentsService, agentConfigService, agentCustomToolsService, agentIntegrationPersistenceService, agentSkillsService, secureRuntime, attachableWorkflowsService, agentsToolsService, builderModelLiveLookupService, mcpRegistryService, oauthService, credentialTypes, agentTaskService, agentPublishService, agentTestRunService, aiService, aiGatewayService, outboundHttp, dynamicNodeParametersService, nodeTypes, ssrfConfig, ssrfProtectionService, freeAiCreditsService, telemetry) {
this.agentsService = agentsService;
this.agentConfigService = agentConfigService;
this.agentCustomToolsService = agentCustomToolsService;
this.agentIntegrationPersistenceService = agentIntegrationPersistenceService;
this.agentSkillsService = agentSkillsService;
this.secureRuntime = secureRuntime;
this.attachableWorkflowsService = attachableWorkflowsService;
this.agentsToolsService = agentsToolsService;
this.builderModelLiveLookupService = builderModelLiveLookupService;
this.mcpRegistryService = mcpRegistryService;
this.oauthService = oauthService;
this.credentialTypes = credentialTypes;
this.agentTaskService = agentTaskService;
this.agentPublishService = agentPublishService;
this.agentTestRunService = agentTestRunService;
this.aiService = aiService;
this.aiGatewayService = aiGatewayService;
this.outboundHttp = outboundHttp;
this.dynamicNodeParametersService = dynamicNodeParametersService;
this.nodeTypes = nodeTypes;
this.ssrfConfig = ssrfConfig;
this.ssrfProtectionService = ssrfProtectionService;
this.freeAiCreditsService = freeAiCreditsService;
this.telemetry = telemetry;
}
withConfigMutationMarker(tool, agentId) {
const handler = tool.handler;
if (!handler)
return tool;
return {
...tool,
handler: async (input, ctx) => {
const result = await handler(input, ctx);
if (typeof result === 'object' &&
result !== null &&
(('ok' in result && result.ok === true) ||
('configured' in result && result.configured === true) ||
('completed' in result && result.completed === true))) {
return { ...result, configMutated: true, agentId };
}
return result;
},
};
}
getTools(agentId, projectId, credentialProvider, user, telemetryContext) {
return {
json: this.getJsonTools(agentId, projectId, credentialProvider, user, telemetryContext),
shared: this.getSharedTools(agentId, projectId, credentialProvider, user),
};
}
getJsonTools(agentId, projectId, credentialProvider, user, telemetryContext) {
const track = (entry, properties) => this.telemetry.track(entry, {
agent_id: agentId,
user_id: user.id,
...(telemetryContext?.threadId ? { thread_id: telemetryContext.threadId } : {}),
...(telemetryContext?.runId ? { run_id: telemetryContext.runId } : {}),
...properties,
});
const readConfigTool = new tool_1.Tool(builder_tool_names_1.BUILDER_TOOLS.READ_CONFIG)
.description('Read the latest persisted agent configuration and its freshness token. ' +
'Returns { ok: true, config, configHash }. This is the only tool that returns the full config — ' +
'write_config, patch_config, and stale responses never echo it back. ' +
'Call this before every write_config or patch_config and use configHash as baseConfigHash.')
.input(zod_1.z.object({}))
.handler(async () => {
try {
const snapshot = await this.getConfigSnapshot(agentId, projectId);
return { ok: true, ...snapshot };
}
catch (e) {
return {
ok: false,
errors: [{ path: '(root)', message: e instanceof Error ? e.message : String(e) }],
};
}
})
.build();
const writeConfigTool = new tool_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 — never from a prior ' +
'write_config/patch_config success or from a stale response. ' +
'Returns { ok: true, configMutated: true, agentId } on success — no config, hash, or timestamps are returned; call ' +
'read_config again before any later inspection or mutation — or ' +
'{ ok: false, stage, errors } with path, message, expected, received fields on failure. ' +
'On stage: "stale", call read_config and retry once using its fresh config and configHash.')
.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, api_types_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] };
}
const zodResult = parseBuilderWriteConfig(parsed.data, snapshot.config);
if (!zodResult.success) {
return { ok: false, errors: (0, api_types_1.formatZodErrors)(zodResult.error) };
}
const emptyInstructions = (0, agent_config_1.rejectIfEmptyInstructions)(zodResult.data, CLI_AGENT_CONFIG_MESSAGES);
if (emptyInstructions) {
return { ok: false, errors: emptyInstructions };
}
const unsupportedNativeWebSearch = (0, agent_config_1.rejectIfUnsupportedNativeWebSearch)(zodResult.data);
if (unsupportedNativeWebSearch) {
return { ok: false, errors: unsupportedNativeWebSearch };
}
const dynamicSelectorFromAi = (0, agent_config_1.rejectIfDynamicSelectorUsesFromAi)(zodResult.data, snapshot.config, this.nodeTypes, CLI_AGENT_CONFIG_MESSAGES);
if (dynamicSelectorFromAi) {
return { ok: false, errors: dynamicSelectorFromAi };
}
const configWithDefaults = applyPromptCachingBuilderDefaults((0, agent_config_1.applyNativeWebSearchDefaultOn)(zodResult.data));
try {
await this.agentConfigService.updateConfig(agentId, projectId, configWithDefaults, user, { modifiedBy: 'builder' });
return { ok: true };
}
catch (e) {
return {
ok: false,
stage: 'schema',
errors: [{ path: '(root)', message: e instanceof Error ? e.message : String(e) }],
};
}
})
.build();
const patchConfigTool = new tool_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 — never from a prior ' +
'write_config/patch_config success or from a stale response. ' +
'Supported ops: add, remove, replace, move, copy, test. ' +
'Returns { ok: true, configMutated: true, agentId } on success — no config, hash, or timestamps are returned; call ' +
'read_config again before any later inspection or mutation — or ' +
'{ ok: false, stage, errors } on failure. ' +
'stage is "parse", "stale", "patch", or "schema". On stage: "stale", call read_config and retry ' +
'once using its fresh config and configHash.')
.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, api_types_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] };
}
if (!snapshot.config) {
return {
ok: false,
stage: 'patch',
errors: [{ path: '(root)', message: 'Agent has no JSON config yet.' }],
};
}
const jsonpatch = (await import('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 = parseBuilderWriteConfig(patched, snapshot.config);
if (!zodResult.success) {
return { ok: false, stage: 'schema', errors: (0, api_types_1.formatZodErrors)(zodResult.error) };
}
const emptyInstructions = (0, agent_config_1.rejectIfEmptyInstructions)(zodResult.data, CLI_AGENT_CONFIG_MESSAGES);
if (emptyInstructions) {
return { ok: false, stage: 'schema', errors: emptyInstructions };
}
const unsupportedNativeWebSearch = (0, agent_config_1.rejectIfUnsupportedNativeWebSearch)(zodResult.data);
if (unsupportedNativeWebSearch) {
return { ok: false, stage: 'schema', errors: unsupportedNativeWebSearch };
}
const dynamicSelectorFromAi = (0, agent_config_1.rejectIfDynamicSelectorUsesFromAi)(zodResult.data, snapshot.config, this.nodeTypes, CLI_AGENT_CONFIG_MESSAGES);
if (dynamicSelectorFromAi) {
return { ok: false, stage: 'schema', errors: dynamicSelectorFromAi };
}
const configWithDefaults = applyPromptCachingBuilderDefaults((0, agent_config_1.applyNativeWebSearchDefaultOn)(zodResult.data));
try {
await this.agentConfigService.updateConfig(agentId, projectId, configWithDefaults, user, { modifiedBy: 'builder' });
return { ok: true };
}
catch (e) {
return {
ok: false,
stage: 'schema',
errors: [{ path: '(root)', message: e instanceof Error ? e.message : String(e) }],
};
}
})
.build();
const listIntegrationTypesTool = new tool_1.Tool(builder_tool_names_1.BUILDER_TOOLS.LIST_INTEGRATION_TYPES)
.description("List integration types that can be added to the agent's `integrations` array. " +
'Returns every available chat platform with the list of ' +
'credential types it supports (`credentialTypes: string[]`) and builder guidance ' +
'(`capabilities`, `useIntegrationWhen`, `useNodeToolWhen`). ' +
'Use that guidance to decide whether the user needs a chat integration or a node tool. ' +
'For a chat integration, pass the selected integration `type` to `configure_channel`; ' +
'never use `ask_credential` for chat-channel credentials.')
.input(zod_1.z.object({}))
.handler(async () => this.agentIntegrationPersistenceService.listChatIntegrations())
.build();
const listSubAgentsTool = new tool_1.Tool(builder_tool_names_1.BUILDER_TOOLS.LIST_SUB_AGENTS)
.description('List published agents in the same project that can be added to the target agent as subagents. ' +
'Excludes the target agent itself and unpublished agents. Use before asking the user which ' +
'subagents to add. Returned `agentId` values are the only valid values to write into `subAgents.agents[].agentId`; ' +
'write parent-owned routing guidance into `subAgents.agents[].useWhen`; ask a follow-up first when it is unclear when that parent should use the subagent.')
.input(zod_1.z.object({}))
.handler(async () => {
const agents = await this.agentsService.findByProjectId(projectId);
return {
agents: agents
.filter((agent) => agent.id !== agentId && agent.activeVersionId !== null)
.map((agent) => ({
agentId: agent.id,
name: agent.name,
})),
};
})
.build();
const publishAgentTool = new tool_1.Tool(builder_tool_names_1.BUILDER_TOOLS.PUBLISH_AGENT)
.description('Publish this target agent so it becomes live: integrations sync and scheduled tasks start running. ' +
'Idempotent when the draft is already the active published version. Pass optional `versionId` to ' +
'activate an existing history row instead of publishing the current draft. Call only when the user ' +
'asks to publish, activate, or make the agent live/usable — never tell them to click Publish in the editor. ' +
'Returns { ok: true, configMutated: true, agentId, activeVersionId, versionId } or { ok: false, errors }.')
.input(zod_1.z.object({
versionId: zod_1.z
.string()
.min(1)
.optional()
.describe('Optional history version ID to activate. Omit to publish the current draft.'),
}))
.handler(async ({ versionId }) => {
if (!(await (0, check_access_1.userHasScopes)(user, ['agent:publish'], false, { projectId }))) {
return {
ok: false,
errors: [{ message: 'You do not have permission to publish agents in this project.' }],
};
}
try {
const { agent } = await this.agentPublishService.publishAgent(agentId, projectId, user, { by: 'builder', trigger: 'explicit' }, versionId);
return {
ok: true,
agentId,
activeVersionId: agent.activeVersionId,
versionId: agent.versionId,
};
}
catch (e) {
return {
ok: false,
errors: [{ message: e instanceof Error ? e.message : String(e) }],
};
}
})
.build();
const unpublishAgentTool = new tool_1.Tool(builder_tool_names_1.BUILDER_TOOLS.UNPUBLISH_AGENT)
.description('Unpublish this target agent: clears the live version while preserving the draft, disconnects chat ' +
'integrations, and stops scheduled tasks. Call when the user asks to unpublish or take the agent offline. ' +
'Returns { ok: true, configMutated: true, agentId, activeVersionId: null } or { ok: false, errors }.')
.input(zod_1.z.object({}))
.handler(async () => {
if (!(await (0, check_access_1.userHasScopes)(user, ['agent:unpublish'], false, { projectId }))) {
return {
ok: false,
errors: [
{ message: 'You do not have permission to unpublish agents in this project.' },
],
};
}
try {
await this.agentPublishService.unpublishAgent(agentId, projectId, user, 'builder');
return { ok: true, agentId, activeVersionId: null };
}
catch (e) {
return {
ok: false,
errors: [{ message: e instanceof Error ? e.message : String(e) }],
};
}
})
.build();
const callAgentTool = new tool_1.Tool(builder_tool_names_1.BUILDER_TOOLS.CALL_AGENT)
.description('Tests the draft agent through built-in Preview chat. It does not test configured channel integrations, including their triggers, platform context, message delivery, or replies. ' +
'Pass the returned sessionId on later calls to continue the same conversation; omit it to start a new one. ' +
'The draft uses its real configured tools and credentials, so external side effects are possible. ' +
'Standard tool approvals pause this test until the user approves or rejects them in chat. ' +
'Unsupported interactive requests return approval_required with a Preview path.')
.input(zod_1.z.object({
message: zod_1.z.string().trim().min(1).describe('Message to send to the target agent'),
sessionId: zod_1.z
.string()
.trim()
.min(1)
.optional()
.describe('Session ID from a previous call_agent result'),
}))
.suspend(tool_1.APPROVAL_SUSPEND_SCHEMA)
.resume(tool_1.APPROVAL_RESUME_SCHEMA)
.handler(async ({ message, sessionId }, ctx) => {
if (!(await (0, check_access_1.userHasScopes)(user, ['agent:execute'], false, { projectId }))) {
return {
status: 'error',
code: 'forbidden',
message: 'You do not have permission to run agents in this project.',
};
}
const previewPath = (0, agent_builder_preview_path_1.buildAgentPreviewPath)(projectId, agentId);
try {
let result;
if (ctx.resumeData === undefined) {
result = await this.agentTestRunService.executeDraftRun({
agentId,
projectId,
message,
sessionId,
credentialProvider,
user,
source: 'instance-ai',
...(ctx.abortSignal ? { abortSignal: ctx.abortSignal } : {}),
});
}
else {
result = await this.agentTestRunService.resumeDraftApproval({
agentId,
projectId,
continuation: ctx.continuation,
approved: ctx.resumeData.approved,
user,
source: 'instance-ai',
...(ctx.abortSignal ? { abortSignal: ctx.abortSignal } : {}),
});
}
if (result.status === 'session_not_found') {
return {
status: 'error',
code: 'session_not_found',
message: 'Session not found.',
};
}
if (result.status === 'agent_misconfigured') {
return {
status: 'error',
code: 'agent_misconfigured',
message: "This agent isn't ready to run yet. Finish configuring it and try again.",
missing: result.missing,
};
}
if (result.status === 'completed')
return result;
const approvals = (0, agent_test_run_service_1.collectStandardApprovals)(result);
const firstApproval = approvals?.[0];
if (firstApproval) {
const { continuation, ...approval } = firstApproval;
return await ctx.suspend(approval, { continuation });
}
const cancelled = await this.agentTestRunService.cancelSuspendedRuns({
agentId,
suspensions: result.suspensions,
userId: user.id,
});
if (!cancelled) {
return {
status: 'error',
code: 'cancellation_failed',
message: 'This test needs approval, but its suspended run could not be cancelled. Open Preview before continuing this session.',
sessionId: result.sessionId,
previewPath,
};
}
return {
status: 'approval_required',
response: result.response,
sessionId: result.sessionId,
...(result.executionId ? { executionId: result.executionId } : {}),
suspensions: result.suspensions.map(({ runId, toolCallId, toolName }) => ({
runId,
toolCallId,
toolName,
})),
previewPath,
};
}
catch (error) {
if (ctx.abortSignal ? ctx.abortSignal.aborted : (0, agents_1.isAbortError)(error))
throw error;
if (error instanceof agent_test_run_service_1.InvalidAgentTestRunCheckpointError) {
return {
status: 'error',
code: error.code,
message: error.message,
};
}
return {
status: 'error',
code: 'execution_failed',
message: error instanceof Error ? error.message : 'Agent test run failed.',
};
}
})
.build();
const modelLookup = {
list: async (credentialId, credentialType, provider) => await this.builderModelLiveLookupService.list(user, projectId, credentialId, credentialType, provider),
};
const tools = [
readConfigTool,
this.withConfigMutationMarker(writeConfigTool, agentId),
this.withConfigMutationMarker(patchConfigTool, agentId),
listIntegrationTypesTool,
listSubAgentsTool,
this.withConfigMutationMarker(publishAgentTool, agentId),
this.withConfigMutationMarker(unpublishAgentTool, agentId),
callAgentTool,
(0, interactive_1.buildResolveLlmTool)({
credentialProvider,
modelLookup,
isProviderServedByGateway: async (provider) => {
try {
return ((await this.aiGatewayService.getCredentialTypeForProvider(provider)) !== undefined);
}
catch {
return false;
}
},
freeCredits: {
isEligible: () => this.freeAiCreditsService.isEligible(user),
claim: async () => {
const credential = await this.freeAiCreditsService.claim(user, projectId);
this.telemetry.track('User claimed OpenAI credits', {
user_id: user.id,
source: 'agentBuilderResolveLlm',
});
return { credentialId: credential.id, credentialName: credential.name };
},
},
}),
(0, interactive_1.buildAskCredentialTool)({
credentialProvider,
isCredentialTypeKnown: (credentialType) => this.credentialTypes.recognizes(credentialType),
listIntegrationCredentialIds: async () => {
const agent = await this.agentsService.findById(agentId, projectId);
return (agent?.integrations ?? [])
.filter((integration) => !(0, api_types_1.isDraftIntegration)(integration))
.map((integration) => integration.credentialId);
},
track,
}),
(0, interactive_1.buildAskEmbeddingCredentialTool)({
credentialProvider,
isCredentialTypeKnown: (credentialType) => this.credentialTypes.recognizes(credentialType),
isAssistantProxyEnabled: () => this.aiService.isProxyEnabled(),
track,
}),
(0, interactive_1.buildAskQuestionsTool)({ track }),
this.withConfigMutationMarker((0, interactive_1.buildConfigureChannelTool)({
agentId,
projectId,
listChatIntegrationTypes: () => this.agentIntegrationPersistenceService
.listChatIntegrations()
.map((integration) => integration.type),
track,
}), agentId),
this.withConfigMutationMarker((0, interactive_1.buildFinishSetupTool)({
credentialProvider,
agentId,
projectId,
track,
isCredentialTypeKnown: (credentialType) => this.credentialTypes.recognizes(credentialType),
listIntegrationCredentialIds: async () => {
const agent = await this.agentsService.findById(agentId, projectId);
return (agent?.integrations ?? [])
.filter((integration) => !(0, api_types_1.isDraftIntegration)(integration))
.map((integration) => integration.credentialId);
},
listChatIntegrationTypes: () => this.agentIntegrationPersistenceService
.listChatIntegrations()
.map((integration) => integration.type),
listAiGatewayManagedCredentialTypes: async () => {
const agent = await this.agentsService.findById(agentId, projectId);
return (0, reconcile_node_tool_gateway_credentials_1.listAiGatewayManagedCredentialTypes)(agent?.schema?.tools, this.nodeTypes);
},
}), agentId),
(0, verify_mcp_server_tool_1.buildVerifyMcpServerTool)({
agentId,
credentialProvider,
oauthService: this.oauthService,
projectId,
proxyFetch: (0, ai_proxy_fetch_1.createAiMcpFetch)(this.outboundHttp, this.ssrfConfig, this.ssrfProtectionService),
applyCredentialToMcpServer: async (serverName, credentialId) => await this.applyCredentialToMcpServer(agentId, projectId, serverName, credentialId, user),
}),
(0, search_mcp_servers_tool_1.buildSearchMcpServersTool)({ mcpRegistryService: this.mcpRegistryService }),
(0, resolve_integration_tool_1.buildResolveIntegrationTool)({
mcpRegistryService: this.mcpRegistryService,
agentsToolsService: this.agentsToolsService,
}),
];
return tools;
}
getSharedTools(agentId, projectId, credentialProvider, user) {
const buildCustomToolTool = new tool_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. The returned `id` equals the tool name ' +
'declared in the code (e.g. `new Tool("my_tool")` → id `"my_tool"`). ' +
'This does NOT register the tool in the agent config — follow up with ' +
'patch_config (or write_config) to add `{ type: "custom", id: "<tool name>" }` ' +
'to `tools`.' +
'Returns { ok: true, id, name } 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 }, ctx) => {
try {
const descriptor = await this.secureRuntime.describeToolSecurely(code);
const built = await this.agentCustomToolsService.buildCustomTool(agentId, projectId, code, descriptor, { user, modifiedBy: 'builder' });
return { ok: true, id: built.id, name: descriptor.name };
}
catch (e) {
if (ctx.abortSignal ? ctx.abortSignal.aborted : (0, agents_1.isAbortError)(e))
throw e;
return {
ok: false,
errors: [{ message: e instanceof Error ? e.message : String(e) }],
};
}
})
.build();
const createSkillsTool = new tool_1.Tool(builder_tool_names_1.BUILDER_TOOLS.CREATE_SKILLS)
.description('Create and store one or more agent skills (reusable, load-on-demand capabilities) in a ' +
'single call. Pass every skill you currently know how to write in one `skills` array — do ' +
"not spread multiple fully-specified skills across separate calls; each skill's instructions " +
'field carries its own structured template. The whole batch is all-or-nothing: an invalid or ' +
'duplicate-named skill rejects every skill in the call. This does NOT attach the skills to the ' +
'agent config; follow up with read_config and patch_config (or write_config) to add a ' +
'`{ type: "skill", id }` entry per skill to `skills`. Returns { ok: true, skills: [{ id, name }, ' +
'...] } (same order as input, bodies are not echoed back) or { ok: false, errors }.')
.systemInstruction('Never create a vague or placeholder skill. The description field is the routing contract the ' +
'runtime uses to decide when to load the skill; the instructions must follow the required ' +
'structured Markdown template (Overview, Inputs, Steps, Rules, Example, Gotchas) with each ' +
'applicable section filled in with concrete, specific content. If you do not have enough domain ' +
"detail to write a genuinely useful skill, derive it from the user's goal as stated assumptions " +
'listed in your summary; ask the user clarifying questions only when even a reasonable ' +
'assumption is impossible. Use allowedTools only with exact target-agent tool names. Use references ' +
'only for markdown supporting files under the references/ directory — references are not ' +
'automatically loaded, so instructions must say exactly when to load each one by path; scripts and ' +
'non-markdown linked files are not supported. Do not invent tool names or reference paths. Batch ' +
'every skill you currently know how to write into one call.')
.input(zod_1.z.object({
skills: zod_1.z
.array(createSkillInputSchema)
.min(1)
.max(20)
.describe('Every skill to create, in the order they should be created.'),
}))
.handler(async ({ skills }) => {
try {
const created = await this.agentSkillsService.createSkills(agentId, projectId, skills, {
user,
modifiedBy: 'builder',
});
return {
ok: true,
skills: created.map(({ id, skill }) => ({ id, name: skill.name })),
};
}
catch (e) {
return {
ok: false,
errors: [{ message: e instanceof Error ? e.message : String(e) }],
};
}
})
.build();
const createTasksTool = new tool_1.Tool(builder_tool_names_1.BUILDER_TOOLS.CREATE_TASKS)
.description('Create one or more recurring scheduled tasks for the target agent (name + objective + cron ' +
'schedule per task) in a single call. Pass every task you currently know how to write in one ' +
"`tasks` array — do not spread multiple fully-specified tasks across separate calls; each task's " +
'objective field carries its own structured template. The whole batch is all-or-nothing: an ' +
'invalid cron or objective rejects every task in the call. This adds a `{ type: "task", id, ' +
'enabled }` ref per task to the agent config (config.tasks) and each task starts running once ' +
'the agent is (re)published via `publish_agent`. Returns { ok: true, configMutated: true, agentId, tasks: [{ id, name, enabled }, ...] } (same ' +
'order as input, objectives and crons are not echoed back) or { ok: false, errors }.')
.systemInstruction('Never create a task with a vague, broad, or placeholder objective, an objective missing any ' +
'required section, or an unclear schedule. Each objective must follow the required structured ' +
'Markdown template (Objective, Context, Steps, Output, Constraints, Success criteria) with every ' +
'section filled in with concrete content — it is the exact, self-contained message the agent ' +
"receives on each unattended run. If anything is ambiguous, derive it from the user's goal as " +
'stated assumptions listed in your summary; ask the user clarifying questions with ask_questions ' +
'only when even a reasonable assumption is impossible, before calling ' +
'create_tasks. A task can only use tools the agent already has: if any step in an objective ' +
'requires a tool, integration, or web search the agent is missing, you MUST add it to the agent ' +
'config (patch_config/write_config) BEFORE calling create_tasks — otherwise the task will fail at ' +
'runtime. Batch every task you currently know how to write into one call.')
.input(zod_1.z.object({
tasks: zod_1.z
.array(zod_1.z.object({
name: api_types_1.agentTaskSchema.shape.name.describe('Short, human-readable task name.'),
objective: api_types_1.agentTaskSchema.shape.objective.describe(task_objective_template_1.TASK_OBJECTIVE_GUIDANCE),
cronExpression: api_types_1.agentTaskSchema.shape.cronExpression.describe('A 5-field cron expression for when the task runs, e.g. "0 9 * * 1-5" = weekdays at 09:00.'),
}))
.min(1)
.max(20)
.describe('Every task to create, in the order they should be created.'),
}))
.handler(async ({ tasks, }) => {
let created;
try {
created = await this.agentTaskService.createTasks(agentId, projectId, tasks.map((task) => ({ ...task, enabled: true })), { user, modifiedBy: 'builder' });
}
catch (e) {
return {
ok: false,
errors: [{ message: e instanceof Error ? e.message : String(e) }],
};
}
return {
ok: true,
tasks: created.map(({ id, name }) => ({ id, name, enabled: true })),
};
})
.build();
const listWorkflowsTool = new tool_1.Tool(builder_tool_names_1.BUILDER_TOOLS.LIST_WORKFLOWS)
.description('List the n8n workflows that can be attached as tools via `type: "workflow"` in the agent config. ' +
'Only returns workflows with supported trigger types. Pass `searchTerm` to narrow by workflow name; ' +
'omitting it returns the 10 most recently updated attachable workflows.')
.input(zod_1.z.object({
searchTerm: zod_1.z
.string()
.optional()
.describe('Optional workflow-name search term. Omit to return the first 10 results.'),
}))
.handler(async ({ searchTerm }) => {
return {
workflows: await this.attachableWorkflowsService.list(user, projectId, searchTerm),
};
})
.build();
return [
buildCustomToolTool,
createSkillsTool,
this.withConfigMutationMarker(createTasksTool, agentId),
listWorkflowsTool,
(0, get_resource_locator_options_tool_1.buildGetResourceLocatorOptionsTool)({
dynamicNodeParametersService: this.dynamicNodeParametersService,
nodeTypes: this.nodeTypes,
user,
projectId,
}),
...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);
}
async applyCredentialToMcpServer(agentId, projectId, serverName, credentialId, user) {
const snapshot = await this.getConfigSnapshot(agentId, projectId);
const config = snapshot.config;
const servers = config?.mcpServers;
if (!config || !servers) {
return { applied: false };
}
const serverIndex = servers.findIndex((server) => server.name === serverName);
if (serverIndex === -1) {
return { applied: false };
}
if (servers[serverIndex]?.credential === credentialId) {
return { applied: false };
}
const patched = {
...config,
mcpServers: servers.map((server, index) => index === serverIndex ? { ...server, credential: credentialId } : server),
};
const zodResult = parseBuilderWriteConfig(patched, snapshot.config);
if (!zodResult.success) {
throw new Error((0, api_types_1.formatZodErrors)(zodResult.error)[0]?.message ?? 'Invalid MCP server config');
}
const configWithDefaults = applyPromptCachingBuilderDefaults((0, agent_config_1.applyNativeWebSearchDefaultOn)(zodResult.data));
await this.agentConfigService.updateConfig(agentId, projectId, configWithDefaults, user, {
modifiedBy: 'builder',
});
return { applied: true };
}
};
exports.AgentsBuilderToolsService = AgentsBuilderToolsService;
exports.AgentsBuilderToolsService = AgentsBuilderToolsService = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [agents_service_1.AgentsService, agent_config_service_1.AgentConfigService, agent_custom_tools_service_1.AgentCustomToolsService, agent_integration_persistence_service_1.AgentIntegrationPersistenceService, agent_skills_service_1.AgentSkillsService, agent_secure_runtime_1.AgentSecureRuntime, attachable_workflows_service_1.AttachableWorkflowsService, agents_tools_service_1.AgentsToolsService, builder_model_live_lookup_service_1.BuilderModelLiveLookupService, mcp_registry_service_1.McpRegistryService, oauth_service_1.OauthService, credential_types_1.CredentialTypes, agent_task_service_1.AgentTaskService, agent_publish_service_1.AgentPublishService, agent_test_run_service_1.AgentTestRunService, ai_service_1.AiService, ai_gateway_service_1.AiGatewayService, backend_network_1.OutboundHttp, dynamic_node_parameters_service_1.DynamicNodeParametersService, node_types_1.NodeTypes, config_1.SsrfProtectionConfig, backend_network_1.SsrfProtectionService, free_ai_credits_service_1.FreeAiCreditsService, telemetry_1.Telemetry])
], AgentsBuilderToolsService);
//# sourceMappingURL=agents-builder-tools.service.js.map