UNPKG

n8n

Version:

n8n Workflow Automation Tool

859 lines 49 kB
"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