UNPKG

@salesforce/agents

Version:

Client side APIs for working with Salesforce agents

296 lines 10.9 kB
"use strict"; 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 = { '&quot;': '"', '&#92;': '\\', '&apos;': "'", '&amp;': '&', '&lt;': '<', '&gt;': '>', '&#39;': "'", '&deg;': '°', '&nbsp;': ' ', '&ndash;': '–', '&mdash;': '—', '&rsquo;': "'", '&lsquo;': "'", '&ldquo;': '"', '&rdquo;': '"', '&hellip;': '…', '&trade;': '™', '&copy;': '©', '&reg;': '®', '&euro;': '€', '&pound;': '£', '&yen;': '¥', '&cent;': '¢', '&times;': '×', '&divide;': '÷', '&plusmn;': '±', '&micro;': 'µ', '&para;': '¶', '&sect;': '§', '&bull;': '•', '&middot;': '·', }; 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