UNPKG

@salesforce/agents

Version:

Client side APIs for working with Salesforce agents

309 lines 14.4 kB
"use strict"; /* * Copyright 2025, Salesforce, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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 __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; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.AgentPublisher = void 0; const path = __importStar(require("node:path")); const promises_1 = require("node:fs/promises"); const node_fs_1 = require("node:fs"); const kit_1 = require("@salesforce/kit"); const fast_xml_parser_1 = require("fast-xml-parser"); const core_1 = require("@salesforce/core"); const source_deploy_retrieve_1 = require("@salesforce/source-deploy-retrieve"); const maybe_mock_1 = require("./maybe-mock"); const utils_1 = require("./utils"); ; const messages = new core_1.Messages('@salesforce/agents', 'agentPublisher', new Map([["agentRetrievalError", "Unable to retrieve newly created Agent metadata. Due to: %s"], ["agentRetrievalErrorActions", "Retrieve the agent metadata using the \"project retrieve start\" command."], ["authoringBundleDeploymentError", "Unable to deploy AiAuthoringBundle metadata. Due to: %s"], ["authoringBundleDeploymentErrorActions", "Deploy the authoring bundle metadata using the \"project deploy start\" command."], ["findBotVersionError", "Unable to find BotVersion with id %s."]])); let logger; const getLogger = () => { if (!logger) { logger = core_1.Logger.childFromRoot('AgentPublisher'); } return logger; }; /** * Service class responsible for publishing agents to Salesforce orgs */ class AgentPublisher { maybeMock; connection; project; agentJson; developerName; bundleMetaPath; bundleDir; API_URL = `https://${kit_1.env.getBoolean('SF_TEST_API') ? 'test.' : ''}api.salesforce.com/einstein/ai-agent/v1.1/authoring/agents`; API_HEADERS = { 'x-client-name': 'afdx', 'content-type': 'application/json', }; /** * Creates a new AgentPublisher instance * * @param connection The connection to the Salesforce org * @param project The Salesforce project */ constructor(connection, project, agentJson) { this.maybeMock = new maybe_mock_1.MaybeMock(connection); this.connection = connection; this.project = project; this.agentJson = agentJson; // Validate and get developer name and bundle directory const validationResult = this.validateDeveloperName(); this.developerName = validationResult.developerName; this.bundleMetaPath = validationResult.bundleMetaPath; this.bundleDir = validationResult.bundleDir; } /** * Publish an AgentJson representation to the org * * @returns Promise<PublishAgent> The publish response */ async publishAgentJson() { getLogger().debug('Publishing Agent'); const body = { agentDefinition: this.agentJson, instanceConfig: { endpoint: this.connection.instanceUrl, }, }; // Use JWT token only for the publish API call, then restore connection // before metadata operations that may use SOAP API let response; try { await (0, utils_1.useNamedUserJwt)(this.connection); const botId = await this.getPublishedBotId(this.developerName); const url = botId ? `${this.API_URL}/${botId}/versions` : this.API_URL; response = await this.maybeMock.request('POST', url, body, this.API_HEADERS); } finally { // Always restore the original connection, even if an error occurred delete this.connection.accessToken; await this.connection.refreshAuth(); } if (response.botId && response.botVersionId) { // we've published the AgentJson, now we need to: // 1. retrieve the new Agent metadata that's in the org // 2. deploy the AuthoringBundle's -meta.xml file with correct target attribute const botVersionName = await this.getVersionDeveloperName(response.botVersionId); await this.retrieveAgentMetadata(botVersionName); await this.syncAuthoringBundle(botVersionName); return { ...response, developerName: this.developerName }; } else { throw core_1.SfError.create({ name: 'CreateAgentJsonError', message: response.errorMessage ?? 'unknown', data: response, }); } } /** * Validates and extracts the developer name from the agent configuration, * and locates the corresponding authoring bundle directory and metadata file. * * @returns An object containing: * - developerName: The cleaned developer name without version suffixes * - bundleDir: The path to the authoring bundle directory * - bundleMetaPath: The full path to the bundle-meta.xml file * * @throws SfError if the authoring bundle directory or metadata file cannot be found */ validateDeveloperName() { const developerName = this.agentJson.globalConfiguration.developerName.replace(/_v\d$/, ''); const defaultPackagePath = path.resolve(this.project.getDefaultPackage().path); // Try to find the authoring bundle directory by recursively searching from the default package path const bundleDir = (0, utils_1.findAuthoringBundle)(defaultPackagePath, developerName); if (!bundleDir) { throw core_1.SfError.create({ name: 'CannotFindBundle', message: `Cannot find an authoring bundle in ${defaultPackagePath} that matches ${developerName}`, }); } const bundleMetaPath = path.join(bundleDir, `${developerName}.bundle-meta.xml`); if (!(0, node_fs_1.existsSync)(bundleMetaPath)) { throw core_1.SfError.create({ name: 'CannotFindBundle', message: `Cannot find a bundle-meta.xml file in ${bundleDir} that matches ${this.developerName}`, }); } return { developerName, bundleDir, bundleMetaPath }; } /** * Retrieve the agent metadata from the org after publishing * * @param developerName The developer name of the agent * @param originalConnection The original connection to use for retrieval */ async retrieveAgentMetadata(botVersionName) { const defaultPackagePath = path.resolve(this.project.getDefaultPackage().path); const cs = await source_deploy_retrieve_1.ComponentSetBuilder.build({ metadata: { metadataEntries: [`Bot:${this.developerName}`, `Agent:${this.developerName}_${botVersionName}`], directoryPaths: [defaultPackagePath], }, org: { username: this.connection.getUsername(), exclude: [], }, }); const retrieve = await cs.retrieve({ usernameOrConnection: this.connection, merge: true, format: 'source', output: path.resolve(this.project.getPath(), defaultPackagePath), }); const retrieveResult = await retrieve.pollStatus(); if (!retrieveResult.response?.success) { const errMessages = retrieveResult.response?.messages?.toString() ?? 'unknown'; const error = messages.createError('agentRetrievalError', [errMessages]); error.actions = [messages.getMessage('agentRetrievalErrorActions')]; throw error; } } /** * Synchronizes the authoring bundle by deploying it twice due to server-side validation requirements. * * Due to a server side validation constraint, the authoring bundle (AAB) must be deployed twice: * 1. First deployment without target attribute creates the AAB as a draft version * 2. Second deployment with target attribute commits the AAB as the published version * * @param botVersionName The bot version name to use as the target for the final published deployment * @private */ async syncAuthoringBundle(botVersionName) { await this.deployAuthoringBundle(); await this.deployAuthoringBundle(botVersionName); } /** * Deploys the authoring bundle to the Salesforce org after setting the correct target attribute if provided. * The target attribute is required for deployment but should not remain in the * local source files after deployment. * * @param botVersionId The bot version ID used to construct the target attribute * * @throws SfError if the deployment fails or if there are component deployment errors */ async deployAuthoringBundle(botVersionName) { // 1. if botVersionName is provided, add the target to the local authoring bundle meta.xml file // 2. deploy the authoring bundle to the org // 3. remove the target from the localauthoring bundle meta.xml file // 1. add the target to the local authoring bundle meta.xml file const xmlParser = new fast_xml_parser_1.XMLParser({ ignoreAttributes: false }); const authoringBundle = xmlParser.parse(await (0, promises_1.readFile)(this.bundleMetaPath, 'utf-8')); if (botVersionName) { const target = `${this.developerName}.${botVersionName}`; authoringBundle.AiAuthoringBundle.target = `${this.developerName}.${botVersionName}`; getLogger().debug(`Setting target to ${target} in ${this.bundleMetaPath}`); } const xmlBuilder = new fast_xml_parser_1.XMLBuilder({ ignoreAttributes: false, format: true, suppressBooleanAttributes: false, suppressEmptyNode: false, }); await (0, promises_1.writeFile)(this.bundleMetaPath, xmlBuilder.build(authoringBundle)); // 2. attempt to deploy the authoring bundle to the org const deploy = await source_deploy_retrieve_1.ComponentSet.fromSource(this.bundleDir).deploy({ usernameOrConnection: this.connection.getUsername(), }); const deployResult = await deploy.pollStatus(); // 3.remove the target from the local authoring bundle meta.xml file delete authoringBundle.AiAuthoringBundle.target; await (0, promises_1.writeFile)(this.bundleMetaPath, xmlBuilder.build(authoringBundle)); if (!deployResult.response?.success) { const componentFailures = deployResult.response.details?.componentFailures; let errMessages = 'unknown'; if (componentFailures) { const failures = Array.isArray(componentFailures) ? componentFailures : [componentFailures]; errMessages = failures[0].problem ?? 'unknown'; } const error = messages.createError('authoringBundleDeploymentError', [errMessages]); error.actions = [messages.getMessage('authoringBundleDeploymentErrorActions')]; throw error; } } /** * Returns the ID for the published bot. * * @param agentApiName The agent API name * @returns The ID for the published bot */ async getPublishedBotId(agentApiName) { try { const queryResult = await this.connection.singleRecordQuery(`SELECT Id FROM BotDefinition WHERE DeveloperName='${agentApiName}'`); getLogger().debug(`Agent with developer name ${agentApiName} and id ${queryResult.Id} is already published.`); return queryResult.Id; } catch (error) { getLogger().debug(`Error reading agent metadata: ${JSON.stringify(error)}`); return undefined; } } /** * Returns the developerName of the given bot version ID. * * @param botVersionId The Id of the bot version * @returns The developer name of the bot version */ async getVersionDeveloperName(botVersionId) { try { const queryResult = await this.connection.singleRecordQuery(`SELECT DeveloperName FROM BotVersion WHERE Id='${botVersionId}'`); getLogger().debug(`Bot version with id ${botVersionId} is ${queryResult.DeveloperName}.`); return queryResult.DeveloperName; } catch (error) { const err = messages.createError('findBotVersionError', [botVersionId]); err.actions = [messages.getMessage('authoringBundleDeploymentErrorActions')]; throw err; } } } exports.AgentPublisher = AgentPublisher; //# sourceMappingURL=agentPublisher.js.map