@salesforce/agents
Version:
Client side APIs for working with Salesforce agents
367 lines • 17 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.decodeResponse = exports.Agent = exports.AgentCreateLifecycleStages = void 0;
const node_util_1 = require("node:util");
const path = __importStar(require("node:path"));
const promises_1 = require("node:fs/promises");
const core_1 = require("@salesforce/core");
const source_deploy_retrieve_1 = require("@salesforce/source-deploy-retrieve");
const kit_1 = require("@salesforce/kit");
const types_1 = require("./types");
const maybe_mock_1 = require("./maybe-mock");
const utils_1 = require("./utils");
const scriptAgent_1 = require("./agents/scriptAgent");
const productionAgent_1 = require("./agents/productionAgent");
const connectionManager_1 = require("./connectionManager");
;
const messages = new core_1.Messages('@salesforce/agents', 'agents', new Map([["invalidAgentSpecConfig", "Missing one or more of the required agent spec arguments: type, role, companyName, companyDescription"], ["missingAgentName", "The \"agentName\" configuration property is required when saving an agent."], ["agentRetrievalError", "Unable to retrieve newly created Agent metadata. Due to: %s"], ["agentRetrievalErrorActions", "Retrieve the agent metadata using the \"project retrieve start\" command."], ["missingAgentNameOrId", "The \"nameOrId\" agent option is required when creating an Agent instance."], ["agentIsDeleted", "The %s agent has been deleted and its activation state can't be changed."], ["agentActivationError", "Changing the agent's activation status was unsuccessful due to %s."], ["versionNotFound", "Version %s not found for this agent."], ["noActiveVersion", "No active version found for agent %s."], ["noVersionsFound", "No versions found for agent %s."]]));
let logger;
const getLogger = () => {
if (!logger) {
logger = core_1.Logger.childFromRoot('Agent');
}
return logger;
};
/**
* Events emitted during Agent.create() for consumers to listen to and keep track of progress
*
* @type {{Creating: string, Previewing: string, Retrieving: string}}
*/
exports.AgentCreateLifecycleStages = {
Creating: 'creatingAgent',
Previewing: 'previewingAgent',
Retrieving: 'retrievingAgent',
};
/**
* A client side representation of an agent. Also provides utilities
* such as creating agents, listing agents, and creating agent specs.
*
* **Examples**
*
* Create a new instance and get the ID (uses the `Bot` ID):
*
* `const id = await Agent.init({connection, project, apiNameOrId: 'myBot' }).getId();`
*
* Create a new instance of an agent script based agent
*
* const agent = await Agent.init({connection, project, aabName: 'myBot' });
*
* Start a preview session
*
* const agent = await Agent.init({connection, project, aabName: 'myBot' });
* await agent.preview.start();
* await agent.preview.send('hi there');
*
* Create a new agent in the org:
*
* `const myAgent = await Agent.create(connection, project, options);`
*
* List all agents in the local project:
*
* `const agentList = await Agent.list(project);`
*/
class Agent {
// Implementation
static async init(options) {
// Pre-warm the per-Connection ConnectionManager cache so the JWT bootstrap and
// standard-connection setup happen here instead of on the first SFAP/SOQL call.
// Subsequent managerFor(options.connection) lookups inside the agent are cache hits.
await (0, connectionManager_1.managerFor)(options.connection);
// Type guard: check if it's ScriptAgentOptions by looking for 'aabName'
if ('aabName' in options) {
return new scriptAgent_1.ScriptAgent(options);
}
else {
const agent = new productionAgent_1.ProductionAgent(options);
await agent.getBotMetadata();
return agent;
}
}
/**
* List all agents in the current project.
*
* @param project a `SfProject` for a local DX project.
*/
static async list(project) {
const projectDirs = project.getPackageDirectories();
const bots = [];
const collectBots = async (botPath) => {
try {
const dirStat = await (0, promises_1.stat)(botPath);
if (!dirStat.isDirectory()) {
return;
}
bots.push(...(await (0, promises_1.readdir)(botPath)));
}
catch (_err) {
// eslint-disable-next-line no-unused-vars
}
};
for (const pkgDir of projectDirs) {
// eslint-disable-next-line no-await-in-loop
await collectBots(path.join(pkgDir.fullPath, 'bots'));
// eslint-disable-next-line no-await-in-loop
await collectBots(path.join(pkgDir.fullPath, 'main', 'default', 'bots'));
}
return bots;
}
/**
* Lists all agents in the org.
*
* @param connection a `Connection` to an org.
* @returns the list of agents
*/
static async listRemote(connection) {
const botDefinitionFields = 'Id, IsDeleted, DeveloperName, MasterLabel, CreatedDate, CreatedById, LastModifiedDate, LastModifiedById, SystemModstamp, BotUserId, Description, Type, AgentType, AgentTemplate';
const botVersionFields = 'Id, Status, IsDeleted, BotDefinitionId, DeveloperName, CreatedDate, CreatedById, LastModifiedDate, LastModifiedById, SystemModstamp, VersionNumber, CopilotPrimaryLanguage, ToneType, CopilotSecondaryLanguages';
const agentsQuery = await connection.query(`SELECT ${botDefinitionFields}, (SELECT ${botVersionFields} FROM BotVersions ORDER BY VersionNumber) FROM BotDefinition`);
return agentsQuery.records;
}
/**
* Lists all agents available for preview, combining agents from the org and local script files.
*
* @param connection a `Connection` to an org.
* @param project a `SfProject` for a local DX project.
* @returns the list of previewable agents with their source (org or script)
*/
static async listPreviewable(connection, project) {
const results = new Array();
(await connection.query("SELECT Id, DeveloperName, MasterLabel FROM BotDefinition WHERE Id IN (SELECT BotDefinitionId FROM BotVersion WHERE Status = 'active')")).records.map((agent) => results.push({
name: agent.MasterLabel,
source: types_1.AgentSource.PUBLISHED,
id: agent.Id,
developerName: agent.DeveloperName,
label: agent.MasterLabel,
}));
// Get local script agents
const projectDirs = project.getPackageDirectories();
const localAgentPaths = new Set();
for (const pkgDir of projectDirs) {
const agentFiles = (0, utils_1.findLocalAgents)(pkgDir.fullPath);
for (const agentFile of agentFiles) {
// Extract the directory path (parent of .agent file)
const agentDir = path.dirname(agentFile);
const agentName = path.basename(agentDir);
const normalizedPath = path.resolve(agentDir);
// Avoid duplicates
if (!localAgentPaths.has(normalizedPath)) {
localAgentPaths.add(normalizedPath);
const previewableAgent = {
name: agentName,
source: types_1.AgentSource.SCRIPT,
aabName: agentName,
};
results.push(previewableAgent);
}
}
}
return results;
}
/**
* Creates an agent from a configuration, optionally saving the agent in an org.
*
* @param connection a `Connection` to an org.
* @param project a `SfProject` for a local DX project.
* @param config a configuration for creating or previewing an agent.
* @returns the agent definition
*/
static async create(connection, project, config) {
const url = '/connect/ai-assist/create-agent';
const connectionManager = await (0, connectionManager_1.managerFor)(connection);
const maybeMock = new maybe_mock_1.MaybeMock(connectionManager.getJwtConnection());
// When previewing agent creation just return the response.
if (!config.saveAgent) {
getLogger().debug(`Previewing agent creation using config: ${(0, node_util_1.inspect)(config)} in project: ${project.getPath()}`);
await core_1.Lifecycle.getInstance().emit(exports.AgentCreateLifecycleStages.Previewing, {});
const response = await maybeMock.request('POST', url, config);
return (0, exports.decodeResponse)(response);
}
if (!config.agentSettings?.agentName) {
throw messages.createError('missingAgentName');
}
getLogger().debug(`Creating agent using config: ${(0, node_util_1.inspect)(config)} in project: ${project.getPath()}`);
await core_1.Lifecycle.getInstance().emit(exports.AgentCreateLifecycleStages.Creating, {});
if (!config.agentSettings.agentApiName) {
config.agentSettings.agentApiName = (0, core_1.generateApiName)(config.agentSettings?.agentName);
}
const response = await maybeMock.request('POST', url, config);
// When saving agent creation we need to retrieve the created metadata.
if (response.isSuccess) {
await core_1.Lifecycle.getInstance().emit(exports.AgentCreateLifecycleStages.Retrieving, {});
const defaultPackagePath = project.getDefaultPackage().path ?? 'force-app';
const standardConnection = connectionManager.getStandardConnection();
try {
const cs = await source_deploy_retrieve_1.ComponentSetBuilder.build({
metadata: {
metadataEntries: [`Agent:${config.agentSettings.agentApiName}`],
directoryPaths: [defaultPackagePath],
},
org: {
username: standardConnection.getUsername(),
exclude: [],
},
});
const retrieve = await cs.retrieve({
usernameOrConnection: standardConnection,
merge: true,
format: 'source',
output: path.resolve(project.getPath(), defaultPackagePath),
});
const retrieveResult = await retrieve.pollStatus({
frequency: kit_1.Duration.milliseconds(200),
timeout: kit_1.Duration.minutes(5),
});
if (!retrieveResult.response.success) {
const errMessages = retrieveResult.response.messages?.toString() ?? 'unknown';
const error = messages.createError('agentRetrievalError', [errMessages]);
error.actions = [messages.getMessage('agentRetrievalErrorActions')];
throw error;
}
}
catch (err) {
const error = core_1.SfError.wrap(err);
if (error.name === 'AgentRetrievalError') {
throw error;
}
throw core_1.SfError.create({
name: 'AgentRetrievalError',
message: messages.getMessage('agentRetrievalError', [error.message]),
cause: error,
actions: [messages.getMessage('agentRetrievalErrorActions')],
});
}
}
return (0, exports.decodeResponse)(response);
}
/**
* Create an agent spec from provided data.
*
* @param connection a `Connection` to an org.
* @param config The configuration used to generate an agent spec.
* @returns the agent job spec
*/
static async createSpec(connection, config) {
const connectionManager = await (0, connectionManager_1.managerFor)(connection);
const maybeMock = new maybe_mock_1.MaybeMock(connectionManager.getJwtConnection());
verifyAgentSpecConfig(config);
const url = '/connect/ai-assist/draft-agent-topics';
const body = {
agentType: config.agentType,
generationInfo: {
defaultInfo: {
role: config.role,
companyName: config.companyName,
companyDescription: config.companyDescription,
},
},
generationSettings: {
maxNumOfTopics: config.maxNumOfTopics ?? 10,
},
};
if (config.companyWebsite) {
body.generationInfo.defaultInfo.companyWebsite = config.companyWebsite;
}
if (config.promptTemplateName) {
body.generationInfo.customizedInfo = { promptTemplateName: config.promptTemplateName };
if (config.groundingContext) {
body.generationInfo.customizedInfo.groundingContext = config.groundingContext;
}
}
const response = await maybeMock.request('POST', url, body);
const htmlDecodedResponse = (0, exports.decodeResponse)(response);
if (htmlDecodedResponse.isSuccess) {
return { ...config, topics: htmlDecodedResponse.topicDrafts };
}
else {
throw core_1.SfError.create({
name: 'AgentJobSpecCreateError',
message: htmlDecodedResponse.errorMessage ?? 'unknown',
data: htmlDecodedResponse,
});
}
}
}
exports.Agent = Agent;
// private function used by Agent.createSpec()
const verifyAgentSpecConfig = (config) => {
const { agentType, role, companyName, companyDescription } = config;
if (!agentType || !role || !companyName || !companyDescription) {
throw messages.createError('invalidAgentSpecConfig');
}
};
// Decodes all HTML entities in ai-assist API responses.
// Recursively decodes HTML entities in all string values (not keys) throughout the object structure.
const decodeResponse = (response) => {
if (response === null || response === undefined) {
return response;
}
// Handle arrays
if (Array.isArray(response)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return response.map((item) => (0, exports.decodeResponse)(item));
}
// Handle primitive values (strings, numbers, booleans, etc.)
if (typeof response !== 'object') {
if (typeof response === 'string') {
return (0, utils_1.decodeHtmlEntities)(response);
}
return response;
}
// Handle objects - only decode values, preserve keys
const decoded = {};
for (const [key, value] of Object.entries(response)) {
// Recursively decode the value, preserving the key as-is
decoded[key] = (0, exports.decodeResponse)(value);
}
return decoded;
};
exports.decodeResponse = decodeResponse;
//# sourceMappingURL=agent.js.map