@salesforce/agents
Version:
Client side APIs for working with Salesforce agents
302 lines • 14.5 kB
JavaScript
;
/*
* Copyright 2026, 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.ScriptAgentPublisher = void 0;
const path = __importStar(require("node:path"));
const promises_1 = require("node:fs/promises");
const node_fs_1 = require("node:fs");
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 connectionManager_1 = require("../connectionManager");
;
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 ScriptAgentPublisher {
connection;
project;
agentJson;
developerName;
bundleMetaPath;
bundleDir;
skipRetrieve;
API_URL;
API_HEADERS = {
'x-client-name': 'afdx',
'content-type': 'application/json',
};
/**
* Creates a new AgentPublisher instance.
*
* @param connection The caller-supplied Connection. Used as the lookup key into the
* ConnectionManager cache (managerFor()); never used directly for SFAP or org API calls.
* @param project The Salesforce project
* @param agentJson The compiled AgentJson to publish
* @param skipMetadataRetrieve Whether to skip retrieving the agent metadata from the org
*/
constructor(connection, project, agentJson, skipMetadataRetrieve = false) {
this.connection = connection;
this.project = project;
this.agentJson = agentJson;
this.skipRetrieve = skipMetadataRetrieve;
this.API_URL = 'https://api.salesforce.com/einstein/ai-agent/v1.1/authoring/agents';
// 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 manager = await (0, connectionManager_1.managerFor)(this.connection);
const body = {
agentDefinition: this.agentJson,
instanceConfig: {
endpoint: manager.getStandardConnection().instanceUrl,
},
};
const botId = await this.getPublishedBotId(this.developerName);
// if we've found a botId in the org, then this agent has already been published before => ai-agent/v1.1/authoring/agents/<id>/versions
// if we didn't find an Id in the org, then we're publishing for the first time => ai-agent/v1.1/authoring/agents
const url = botId ? `${this.API_URL}/${botId}/versions` : this.API_URL;
const maybeMock = new maybe_mock_1.MaybeMock(manager.getJwtConnection());
const response = await maybeMock.request('POST', url, body, this.API_HEADERS);
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);
if (!this.skipRetrieve) {
await this.retrieveAgentMetadata(botVersionName);
}
await this.deployAuthoringBundle(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 agent's developer name
* - 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;
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 botVersionName The bot version name
*/
async retrieveAgentMetadata(botVersionName) {
const standardConn = (await (0, connectionManager_1.managerFor)(this.connection)).getStandardConnection();
const defaultPackagePath = path.resolve(this.project.getDefaultPackage().path);
const genAiPluginAndFunctions = this.agentJson.agentVersion.nodes.flatMap((n) => [
`GenAiPlugin:${n.developerName}`,
...n.tools.map((t) => `GenAiFunction:${t.name}`),
]);
const cs = await source_deploy_retrieve_1.ComponentSetBuilder.build({
metadata: {
metadataEntries: [
`Bot:${this.developerName}`,
...genAiPluginAndFunctions,
`Agent:${this.developerName}_${botVersionName}`,
],
directoryPaths: [defaultPackagePath],
},
org: {
username: standardConn.getUsername(),
exclude: [],
},
});
const retrieve = await cs.retrieve({
usernameOrConnection: standardConn,
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;
}
}
/**
* Deploys the authoring bundle to the Salesforce org after setting the correct target attribute.
* The target attribute is required for deployment but should not remain in the
* local source files after deployment.
*
* @throws SfError if the deployment fails or if there are component deployment errors
* @param botVersionName
*/
async deployAuthoringBundle(botVersionName) {
// 1. 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'));
const target = `${this.developerName}.${botVersionName}`;
authoringBundle.AiAuthoringBundle.target = target;
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 standardConn = (await (0, connectionManager_1.managerFor)(this.connection)).getStandardConnection();
const deploy = await source_deploy_retrieve_1.ComponentSet.fromSource(this.bundleDir).deploy({
usernameOrConnection: standardConn,
});
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 standardConn = (await (0, connectionManager_1.managerFor)(this.connection)).getStandardConnection();
const queryResult = await standardConn.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 standardConn = (await (0, connectionManager_1.managerFor)(this.connection)).getStandardConnection();
const queryResult = await standardConn.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.ScriptAgentPublisher = ScriptAgentPublisher;
//# sourceMappingURL=scriptAgentPublisher.js.map