@salesforce/agents
Version:
Client side APIs for working with Salesforce agents
296 lines • 10.9 kB
JavaScript
;
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.readTranscriptEntries = exports.appendTranscriptEntry = exports.useNamedUserJwt = exports.findLocalAgents = exports.findAuthoringBundle = exports.decodeHtmlEntities = exports.sanitizeFilename = exports.metric = void 0;
/*
* 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.
*/
const node_fs_1 = require("node:fs");
const promises_1 = require("node:fs/promises");
const path = __importStar(require("node:path"));
const core_1 = require("@salesforce/core");
exports.metric = ['completeness', 'coherence', 'conciseness', 'output_latency_milliseconds'];
/**
* Sanitize a filename by removing or replacing illegal characters.
* This ensures the filename is valid across different operating systems.
*
* @param filename - The filename to sanitize
* @returns A sanitized filename safe for use across operating systems
*/
const sanitizeFilename = (filename) => {
if (!filename)
return '';
// Replace colons from ISO timestamps with underscores
const sanitized = filename.replace(/:/g, '_');
// Replace other potentially problematic characters
return sanitized.replace(/[<>:"\\|?*]/g, '_');
};
exports.sanitizeFilename = sanitizeFilename;
/**
* Clean a string by replacing HTML entities with their respective characters.
*
* @param str - The string to clean.
* @returns The cleaned string with all HTML entities replaced with their respective characters.
*/
const decodeHtmlEntities = (str = '') => {
const entities = {
'"': '"',
'\': '\\',
''': "'",
'&': '&',
'<': '<',
'>': '>',
''': "'",
'°': '°',
' ': ' ',
'–': '–',
'—': '—',
'’': "'",
'‘': "'",
'“': '"',
'”': '"',
'…': '…',
'™': '™',
'©': '©',
'®': '®',
'€': '€',
'£': '£',
'¥': '¥',
'¢': '¢',
'×': '×',
'÷': '÷',
'±': '±',
'µ': 'µ',
'¶': '¶',
'§': '§',
'•': '•',
'·': '·',
};
return str.replace(/&[a-zA-Z0-9#]+;/g, (entity) => entities[entity] || entity);
};
exports.decodeHtmlEntities = decodeHtmlEntities;
/**
* Find the authoring bundle directory for a given bot name by recursively searching from a starting directory or directories.
*
* @param dirOrDirs - The directory or array of directories to start searching from
* @param botName - The name of the bot to find the authoring bundle directory for
* @returns The path to the authoring bundle directory if found, undefined otherwise
*/
const findAuthoringBundle = (dirOrDirs, botName) => {
// If it's an array of directories, search in each one
if (Array.isArray(dirOrDirs)) {
for (const dir of dirOrDirs) {
const found = (0, exports.findAuthoringBundle)(dir, botName);
if (found)
return found;
}
return undefined;
}
// Single directory search logic
const dir = dirOrDirs;
try {
const files = (0, node_fs_1.readdirSync)(dir);
// If we find aiAuthoringBundles dir, check for the expected directory structure
if (files.includes('aiAuthoringBundles')) {
const expectedPath = path.join(dir, 'aiAuthoringBundles', botName);
const statResult = (0, node_fs_1.statSync)(expectedPath, { throwIfNoEntry: false });
if (statResult?.isDirectory()) {
return expectedPath;
}
}
// Otherwise keep searching directories
for (const file of files) {
const filePath = path.join(dir, file);
const statResult = (0, node_fs_1.statSync)(filePath, { throwIfNoEntry: false });
if (statResult?.isDirectory()) {
const found = (0, exports.findAuthoringBundle)(filePath, botName);
if (found)
return found;
}
}
}
catch (err) {
// Directory doesn't exist or can't be read
return undefined;
}
return undefined;
};
exports.findAuthoringBundle = findAuthoringBundle;
/**
* Find all local agent files in a directory by recursively searching for files ending with '.agent'
*
* @param dir - The directory to start searching from
* @returns Array of paths to agent files
*/
const findLocalAgents = (dir) => {
const results = [];
try {
const files = (0, node_fs_1.readdirSync)(dir);
for (const file of files) {
const filePath = path.join(dir, file);
const statResult = (0, node_fs_1.statSync)(filePath, { throwIfNoEntry: false });
if (!statResult)
continue;
if (statResult.isDirectory()) {
results.push(...(0, exports.findLocalAgents)(filePath));
}
else if (file.endsWith('.agent')) {
results.push(filePath);
}
}
}
catch (err) {
// Directory doesn't exist or can't be read
return [];
}
return results;
};
exports.findLocalAgents = findLocalAgents;
const useNamedUserJwt = async (connection) => {
// Refresh the connection to ensure we have the latest, valid access token
try {
await connection.refreshAuth();
}
catch (error) {
throw core_1.SfError.create({
name: 'ApiAccessError',
message: 'Error refreshing connection',
cause: error,
});
}
const { accessToken, instanceUrl } = connection.getConnectionOptions();
if (!instanceUrl) {
throw core_1.SfError.create({
name: 'ApiAccessError',
message: 'Missing Instance URL for org connection',
});
}
if (!accessToken) {
throw core_1.SfError.create({
name: 'ApiAccessError',
message: 'Missing Access Token for org connection',
});
}
const url = `${instanceUrl}/agentforce/bootstrap/nameduser`;
// For the namdeduser endpoint request to work we need to delete the access token
delete connection.accessToken;
try {
const response = await connection.request({
method: 'GET',
url,
headers: {
'Content-Type': 'application/json',
Cookie: `sid=${accessToken}`,
},
}, { retry: { maxRetries: 3 } });
connection.accessToken = response.access_token;
return connection;
}
catch (error) {
throw core_1.SfError.create({
name: 'ApiAccessError',
message: 'Error obtaining API token',
cause: error,
});
}
};
exports.useNamedUserJwt = useNamedUserJwt;
const resolveProjectLocalSfdx = async () => {
try {
const project = await core_1.SfProject.resolve();
return path.join(project.getPath(), '.sfdx');
}
catch (_e) {
return undefined;
}
};
const getConversationDir = async (agentId) => {
const base = (await resolveProjectLocalSfdx()) ?? path.join(process.cwd(), '.sfdx');
const dir = path.join(base, 'agents', agentId);
await (0, promises_1.mkdir)(dir, { recursive: true });
return dir;
};
const getLastConversationPath = async (agentId) => path.join(await getConversationDir(agentId), 'history.json');
/**
* Append a transcript entry to the last conversation JSON file under the project local .sfdx folder.
* If the entry has event: 'start', this will clear the previous conversation and start fresh.
* Path: <project>/.sfdx/agents/conversations/<agentId>/history.json
*/
const appendTranscriptEntry = async (entry, newSession = false) => {
const filePath = await getLastConversationPath(entry.agentId);
const line = `${JSON.stringify(entry)}\n`;
// If this is a new session start, clear the file first
if (newSession) {
await (0, promises_1.writeFile)(filePath, line, 'utf-8');
}
else {
await (0, promises_1.appendFile)(filePath, line);
}
};
exports.appendTranscriptEntry = appendTranscriptEntry;
/**
* Read and parse the last conversation's transcript entries from JSON.
* Path: <project>/.sfdx/agents/conversations/<agentId>/history.json
*
* @param agentId The agent's API name (developerName)
* @returns Array of TranscriptEntry in file order (chronological append order).
*/
const readTranscriptEntries = async (agentId) => {
const filePath = await getLastConversationPath(agentId);
try {
const data = await (0, promises_1.readFile)(filePath, 'utf-8');
return data
.split('\n')
.filter((l) => l.trim().length > 0)
.map((l) => JSON.parse(l));
}
catch (_e) {
return [];
}
};
exports.readTranscriptEntries = readTranscriptEntries;
//# sourceMappingURL=utils.js.map