@salesforce/agents
Version:
Client side APIs for working with Salesforce agents
183 lines • 6.77 kB
JavaScript
;
/*
* 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.AgentTester = void 0;
exports.normalizeResults = normalizeResults;
const core_1 = require("@salesforce/core");
const kit_1 = require("@salesforce/kit");
const maybe_mock_1 = require("./maybe-mock");
const utils_1 = require("./utils");
/**
* A service for testing agents using `AiEvaluationDefinition` metadata. Start asynchronous
* test runs, get or poll for test status, and get detailed test results.
*
* **Examples**
*
* Create an instance of the service:
*
* `const agentTester = new AgentTester(connection);`
*
* Start a test run:
*
* `const startResponse = await agentTester.start(aiEvalDef);`
*
* Get the status for a test run:
*
* `const status = await agentTester.status(startResponse.runId);`
*
* Get detailed results for a test run:
*
* `const results = await agentTester.results(startResponse.runId);`
*/
class AgentTester {
maybeMock;
constructor(connection) {
this.maybeMock = new maybe_mock_1.MaybeMock(connection);
}
/**
* Initiates a test run (i.e., AI evaluation).
*
* @param aiEvalDefName - The name of the AI evaluation definition to run.
* @returns Promise that resolves with the response from starting the test.
*/
async start(aiEvalDefName) {
const url = '/einstein/ai-evaluations/runs';
return this.maybeMock.request('POST', url, {
aiEvaluationDefinitionName: aiEvalDefName,
});
}
/**
* Get the status of a test run.
*
* @param {string} jobId
* @returns {Promise<AgentTestStatusResponse>}
*/
async status(jobId) {
const url = `/einstein/ai-evaluations/runs/${jobId}`;
return this.maybeMock.request('GET', url);
}
/**
* Poll the status of a test run until the tests are complete or the timeout is reached.
*
* @param {string} jobId
* @param {Duration} timeout
* @returns {Promise<AgentTestResultsResponse>}
*/
async poll(jobId, { timeout = kit_1.Duration.minutes(5), } = {
timeout: kit_1.Duration.minutes(5),
}) {
const frequency = kit_1.env.getNumber('SF_AGENT_TEST_POLLING_FREQUENCY_MS', 1000);
const lifecycle = core_1.Lifecycle.getInstance();
const client = await core_1.PollingClient.create({
poll: async () => {
const statusResponse = await this.status(jobId);
if (statusResponse.status.toLowerCase() !== 'new') {
const resultsResponse = await this.results(jobId);
const totalTestCases = resultsResponse.testCases.length;
const passingTestCases = resultsResponse.testCases.filter((tc) => tc.status.toLowerCase() === 'completed' && tc.testResults.every((r) => r.result === 'PASS')).length;
const failingTestCases = resultsResponse.testCases.filter((tc) => ['error', 'completed'].includes(tc.status.toLowerCase()) &&
tc.testResults.some((r) => r.result === 'FAILURE')).length;
if (resultsResponse.status.toLowerCase() === 'completed') {
await lifecycle.emit('AGENT_TEST_POLLING_EVENT', {
jobId,
status: resultsResponse.status,
totalTestCases,
failingTestCases,
passingTestCases,
});
return { payload: resultsResponse, completed: true };
}
await lifecycle.emit('AGENT_TEST_POLLING_EVENT', {
jobId,
status: resultsResponse.status,
totalTestCases,
failingTestCases,
passingTestCases,
});
}
return { completed: false };
},
frequency: kit_1.Duration.milliseconds(frequency),
timeout,
});
return client.subscribe();
}
/**
* Get detailed test run results.
*
* @param {string} jobId
* @returns {Promise<AgentTestResultsResponse>}
*/
async results(jobId) {
const url = `/einstein/ai-evaluations/runs/${jobId}/results`;
const results = await this.maybeMock.request('GET', url);
return normalizeResults(results);
}
/**
* Cancel an in-progress test run.
*
* @param {string} jobId
* @returns {Promise<{success: boolean}>}
*/
async cancel(jobId) {
const url = `/einstein/ai-evaluations/runs/${jobId}/cancel`;
return this.maybeMock.request('POST', url);
}
}
exports.AgentTester = AgentTester;
/**
* Normalizes test results by decoding HTML entities in utterances and test result values.
*
* @param results - The agent test results response object to normalize
* @returns A new AgentTestResultsResponse with decoded HTML entities
*
* @example
* ```
* const results = {
* testCases: [{
* inputs: { utterance: ""hello"" },
* testResults: [{
* actualValue: "&test",
* expectedValue: "<value>"
* }]
* }]
* };
* const normalized = normalizeResults(results);
* ```
*/
function normalizeResults(results) {
return {
...results,
testCases: results.testCases.map((tc) => ({
...tc,
generatedData: {
...tc.generatedData,
invokedActions: (0, utils_1.decodeHtmlEntities)(tc.generatedData.invokedActions),
},
inputs: {
utterance: (0, utils_1.decodeHtmlEntities)(tc.inputs.utterance),
},
testResults: tc.testResults.map((r) => ({
...r,
actualValue: (0, utils_1.decodeHtmlEntities)(r.actualValue),
expectedValue: (0, utils_1.decodeHtmlEntities)(r.expectedValue),
metricExplainability: (0, utils_1.decodeHtmlEntities)(r.metricExplainability),
})),
})),
};
}
//# sourceMappingURL=agentTester.js.map