UNPKG

@salesforce/agents

Version:

Client side APIs for working with Salesforce agents

297 lines 12.9 kB
"use strict"; /* * 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. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.AgentSimulate = void 0; const promises_1 = require("node:fs/promises"); const node_path_1 = require("node:path"); const node_crypto_1 = require("node:crypto"); const core_1 = require("@salesforce/core"); const kit_1 = require("@salesforce/kit"); const agent_1 = require("./agent"); const agentPreviewBase_1 = require("./agentPreviewBase"); const apexUtils_1 = require("./apexUtils"); const utils_1 = require("./utils"); /** * A service to simulate interactions with an agent using a local .agent file. * The file will be compiled using Agent.compileAgent before being used * with the simulation endpoints. * * **Examples** * * Create an instance of the service: * * `const agentSimulate = new AgentSimulate(connection, './path/to/agent.agent');` * * Start an interactive session: * * `const { sessionId } = await agentSimulate.start();` * * Send a message to the agent using the session ID from the startResponse: * * `const sendResponse = await agentSimulate.send(sessionId, message);` * * End an interactive session: * * `await agentSimulate.end(sessionId, 'UserRequest');` * * Enable Apex Debug Mode: * * `agentSimulate.toggleApexDebugMode(true);` */ class AgentSimulate extends agentPreviewBase_1.AgentPreviewBase { /** * The client can specify whether the actions will run in a simulated mode ("mock actions", no side effects, mockActions=true) or a non-simulated mode ("real actions", actions with side effects, mockActions=false) */ mockActions; apiBase = `https://${kit_1.env.getBoolean('SF_TEST_API') ? 'test.' : ''}api.salesforce.com/einstein/ai-agent`; agentFilePath; compiledAgent; /** * Create an instance of the service. * * @param connection The connection to use to make requests. * @param agentFilePath Path to the .agent file to simulate. * @param mockActions whether or not to mock the actions of the simulated agent */ constructor(connection, agentFilePath, mockActions) { super({ connection }); this.agentFilePath = agentFilePath; this.mockActions = mockActions; } /** * Start an interactive simulation session with the agent. * This will first compile the agent script if it hasn't been compiled yet. * * @returns `AgentPreviewStartResponse`, which includes a session ID needed for other actions. */ async start() { if (!this.compiledAgent) { void core_1.Lifecycle.getInstance().emit('agents:compiling', {}); this.logger.debug(`Compiling agent script from ${this.agentFilePath}`); const agentString = await (0, promises_1.readFile)(this.agentFilePath, 'utf-8'); const compiledAgent = await agent_1.Agent.compileAgentScript(this.connection, agentString); if (compiledAgent.status === 'success' && compiledAgent.compiledArtifact) { this.compiledAgent = compiledAgent.compiledArtifact; // we must set the compiledAgent.agentVersion.developerName, we'll get this from the -meta <target> field const metaContent = await (0, promises_1.readFile)(`${this.agentFilePath.replace('.agent', '.bundle-meta.xml')}`, 'utf-8'); this.compiledAgent.agentVersion.developerName = metaContent.match(/<target>.*(v\d+)<\/target>/)?.at(1) ?? 'v0'; } else { const formattedError = compiledAgent.errors .map((e) => `- ${e.errorType} ${e.description}: ${e.lineStart}:${e.colStart} / ${e.lineEnd}:${e.colEnd}`) .join('\n'); throw new core_1.SfError('Failed to compile agent script', formattedError); } } this.logger.debug('Starting agent simulation session'); // send bypassUser=false when the compiledAgent.globalConfiguration.defaultAgentUser is INVALID let bypassUser = (await this.connection.query(`SELECT Id FROM USER WHERE username='${this.compiledAgent.globalConfiguration.defaultAgentUser}'`)).totalSize === 1; if (bypassUser && this.compiledAgent.globalConfiguration.agentType === 'AgentforceEmployeeAgent') { // another situation which bypassUser = false, is when previewing an agent script, with a valid default_agent_user, and it's an AgentforceEmployeeAgent type bypassUser = false; } const body = { agentDefinition: this.compiledAgent, enableSimulationMode: this.mockActions, externalSessionKey: (0, node_crypto_1.randomUUID)(), instanceConfig: { endpoint: this.connection.instanceUrl, }, variables: [], parameters: {}, streamingCapabilities: { chunkTypes: ['Text', 'LightningChunk'], }, richContentCapabilities: {}, bypassUser, executionHistory: [], conversationContext: [], }; try { void core_1.Lifecycle.getInstance().emit('agents:simulation-starting', {}); this.connection = await (0, utils_1.useNamedUserJwt)(this.connection); const response = await this.connection.request({ method: 'POST', url: `${this.apiBase}/v1.1/preview/sessions`, headers: { 'x-attributed-client': 'no-builder', // <- removes markdown from responses 'x-client-name': 'afdx', }, body: JSON.stringify(body), }, { retry: { maxRetries: 3 } }); const agentIdForStorage = (0, node_path_1.basename)(this.agentFilePath); await (0, utils_1.appendTranscriptEntry)({ timestamp: new Date().toISOString(), agentId: agentIdForStorage, sessionId: response.sessionId, role: 'agent', text: response.messages.map((m) => m.message).join('\n'), raw: response.messages, }, true); return response; } catch (err) { throw core_1.SfError.wrap(err); } finally { // Always restore the original connection, even if an error occurred delete this.connection.accessToken; await this.connection.refreshAuth(); } } /** * Send a message to the agent using the session ID obtained by calling `start()`. * * @param sessionId A session ID provided by first calling `agentSimulate.start()`. * @param message A message to send to the agent. * @returns `AgentPreviewSendResponse` */ async send(sessionId, message) { if (!this.compiledAgent) { throw new core_1.SfError('Agent not compiled, please call .start() first'); } const url = `${this.apiBase}/v1.1/preview/sessions/${sessionId}/messages`; const body = { message: { sequenceId: Date.now(), type: 'Text', text: message, }, variables: [], }; this.logger.debug(`Sending message with apexDebugMode ${this.apexDebugMode ? 'enabled' : 'disabled'}`); const agentIdForStorage = (0, node_path_1.basename)(this.agentFilePath); try { const start = Date.now(); if (this.apexDebugMode && !this.mockActions) { await this.ensureTraceFlag(); } await (0, utils_1.appendTranscriptEntry)({ timestamp: new Date().toISOString(), agentId: agentIdForStorage, sessionId, role: 'user', text: message, }); this.connection = await (0, utils_1.useNamedUserJwt)(this.connection); const response = await this.connection.request({ method: 'POST', url, body: JSON.stringify(body), headers: { 'x-client-name': 'afdx', }, }); await (0, utils_1.appendTranscriptEntry)({ timestamp: new Date().toISOString(), agentId: agentIdForStorage, sessionId, role: 'agent', text: response.messages.map((m) => m.message).join('\n'), raw: response.messages, }); if (this.apexDebugMode && !this.mockActions) { const apexLog = await (0, apexUtils_1.getDebugLog)(this.connection, start, Date.now()); if (apexLog) { if (apexLog.Id) this.logger.debug(`Apex debug log ID for message is ${apexLog.Id}`); response.apexDebugLog = apexLog; } else { this.logger.debug('No apex debug log found for this message'); } } return response; } catch (err) { throw core_1.SfError.wrap(err); } finally { // Always restore the original connection, even if an error occurred delete this.connection.accessToken; await this.connection.refreshAuth(); } } /** * Ending is not required, or supported, for AgentSimulation * this is a noop method to support easier consumer typings * * @returns `AgentPreviewEndResponse` */ // eslint-disable-next-line class-methods-use-this async end() { return Promise.resolve({ messages: [], _links: [] }); } /** * Enable or disable Apex Debug Mode, which will enable trace flags for the Bot user * and create apex debug logs for use within VS Code's Apex Replay Debugger. * * @param enable Whether to enable or disable Apex Debug Mode. */ toggleApexDebugMode(enable) { this.setApexDebugMode(enable); } async trace(sessionId, messageId) { const traces = await this.traces(sessionId, [messageId]); return traces.length ? traces[0] : null; } async traces(sessionId, messageIds) { try { this.connection = await (0, utils_1.useNamedUserJwt)(this.connection); const traces = []; const tracePromises = messageIds.map((messageId) => this.connection.request({ method: 'GET', url: `${this.apiBase}/v1.1/preview/sessions/${sessionId}/plans/${messageId}`, headers: { 'x-client-name': 'afdx', }, })); const traceResults = await Promise.all(tracePromises); traces.push(...traceResults); return traces; } catch (err) { throw core_1.SfError.wrap(err); } finally { // Always restore the original connection, even if an error occurred delete this.connection.accessToken; await this.connection.refreshAuth(); } } // once we're previewing agents in the org, with mockActions = false, we'll have to figure out how to get the correct user that was simulated for apex invocattion async ensureTraceFlag() { if (this.apexTraceFlag) { const expDate = this.apexTraceFlag.ExpirationDate; if (expDate && new Date(expDate) > new Date()) { this.logger.debug(`Using cached apexTraceFlag with ExpirationDate of ${expDate}`); return; } else { this.logger.debug('Cached apex trace flag is expired'); } } const user = this.compiledAgent?.globalConfiguration.defaultAgentUser ?? this.connection.getUsername(); const userId = (await this.connection.singleRecordQuery(`SELECT Id FROM User WHERE Username = '${user}'`)).Id; this.apexTraceFlag = await (0, apexUtils_1.findTraceFlag)(this.connection, userId); if (!this.apexTraceFlag) { await (0, apexUtils_1.createTraceFlag)(this.connection, userId); } } } exports.AgentSimulate = AgentSimulate; //# sourceMappingURL=agentSimulate.js.map