@salesforce/agents
Version:
Client side APIs for working with Salesforce agents
151 lines • 6.14 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.AgentforceStudioTester = void 0;
exports.normalizeAgentforceStudioResults = normalizeAgentforceStudioResults;
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 `AiTestingDefinition` metadata (Agentforce Studio).
* 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 AgentforceStudioTester(connection);`
*
* Start a test run:
*
* `const startResponse = await agentTester.start(aiTestSuiteDefName);`
*
* 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 AgentforceStudioTester {
maybeMock;
constructor(connection) {
this.maybeMock = new maybe_mock_1.MaybeMock(connection);
}
/**
* Initiates a test run (i.e., AI test suite evaluation).
*
* @param testDefinitionName - The name of the AI test suite definition to run.
* @returns Promise that resolves with the response from starting the test.
*/
async start(testDefinitionName) {
const url = '/einstein/ai-testing/runs';
const result = await this.maybeMock.request('POST', url, {
testDefinitionName,
});
if (result?.runId === undefined) {
throw core_1.SfError.create({ name: 'TestInProgress', message: 'a test run is already in progress' });
}
return result;
}
/**
* Get the status of a test run.
*
* @param {string} runId
* @returns {Promise<AgentforceStudioTestStatusResponse>}
*/
async status(runId) {
const url = `/einstein/ai-testing/runs/${runId}`;
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} runId
* @param {Duration} timeout
* @returns {Promise<AgentforceStudioTestResultsResponse>}
*/
async poll(runId, { 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(runId);
if (statusResponse.status.toLowerCase() !== 'new') {
const resultsResponse = await this.results(runId);
const totalTestCases = resultsResponse.testCases.length;
const isPassingScorer = (scorerResponse) => {
try {
const { actualValue, expectedValue } = JSON.parse(scorerResponse);
return actualValue !== undefined && actualValue === expectedValue;
}
catch {
return false;
}
};
const passingTestCases = resultsResponse.testCases.filter((tc) => tc.testScorerResults.length > 0 && tc.testScorerResults.every((s) => isPassingScorer(s.scorerResponse))).length;
const failingTestCases = totalTestCases - passingTestCases;
await lifecycle.emit('AGENT_TEST_POLLING_EVENT', {
// to match the other AGENT_TEST_POLLING_EVENT for consumers
jobId: runId,
status: resultsResponse.status,
totalTestCases,
failingTestCases,
passingTestCases,
});
const terminalStatuses = ['SUCCESS', 'FAILED', 'TERMINATED'];
if (terminalStatuses.includes(resultsResponse.status)) {
return { payload: resultsResponse, completed: true };
}
}
return { completed: false };
},
frequency: kit_1.Duration.milliseconds(frequency),
timeout,
});
return client.subscribe();
}
/**
* Get detailed test run results.
*
* @param {string} runId
* @returns {Promise<AgentforceStudioTestResultsResponse>}
*/
async results(runId) {
const url = `/einstein/ai-testing/runs/${runId}/results`;
const results = await this.maybeMock.request('GET', url);
return normalizeAgentforceStudioResults(results);
}
}
exports.AgentforceStudioTester = AgentforceStudioTester;
/** Decodes HTML entities in test result subject responses and scorer responses. */
function normalizeAgentforceStudioResults(results) {
return {
...results,
testCases: results.testCases.map((tc) => ({
...tc,
subjectResponse: (0, utils_1.decodeHtmlEntities)(tc.subjectResponse),
testScorerResults: tc.testScorerResults.map((scorer) => ({
...scorer,
scorerResponse: (0, utils_1.decodeHtmlEntities)(scorer.scorerResponse),
})),
})),
};
}
//# sourceMappingURL=agentforceStudioTester.js.map