@salesforce/agents
Version:
Client side APIs for working with Salesforce agents
309 lines • 14.4 kB
JavaScript
;
/*
* 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