@salesforce/agents
Version:
Client side APIs for working with Salesforce agents
314 lines • 13.4 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.AgentDataLibrary = void 0;
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const core_1 = require("@salesforce/core");
function baseUrl(connection, libraryId) {
const base = `/services/data/v${String(connection.version)}/einstein/data-libraries`;
return libraryId ? `${base}/${libraryId}` : base;
}
class AgentDataLibrary {
static async list(connection, options) {
try {
let url = baseUrl(connection);
if (options?.sourceType) {
url += `?sourceType=${options.sourceType.toUpperCase()}`;
}
return await connection.request({
method: 'GET',
url,
});
}
catch (error) {
const wrapped = core_1.SfError.wrap(error);
await core_1.Lifecycle.getInstance().emitTelemetry({ eventName: 'agent_adl_list_failed' });
throw wrapped;
}
}
static async create(connection, input) {
let result;
try {
result = await connection.request({
method: 'POST',
url: baseUrl(connection),
body: JSON.stringify(input),
headers: { 'Content-Type': 'application/json' },
});
}
catch (error) {
const wrapped = core_1.SfError.wrap(error);
await core_1.Lifecycle.getInstance().emitTelemetry({ eventName: 'agent_adl_create_failed' });
throw wrapped;
}
await core_1.Lifecycle.getInstance().emitTelemetry({ eventName: 'agent_adl_create_success' });
if (input.groundingSource.sourceType === 'KNOWLEDGE') {
// Trigger indexing — abort after 10s to avoid blocking process exit.
// Server starts processing on receipt; we don't need the response.
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10_000);
try {
await fetch(`${String(connection.instanceUrl)}${baseUrl(connection, result.libraryId)}/indexing`, {
method: 'POST',
headers: {
Authorization: `Bearer ${String(connection.accessToken)}`,
'Content-Type': 'application/json',
},
signal: controller.signal,
});
}
catch {
// best-effort — server processes indexing even if aborted/timeout
}
finally {
clearTimeout(timeout);
}
}
return result;
}
static async get(connection, libraryId) {
try {
return await connection.request({
method: 'GET',
url: baseUrl(connection, libraryId),
});
}
catch (error) {
const wrapped = core_1.SfError.wrap(error);
await core_1.Lifecycle.getInstance().emitTelemetry({ eventName: 'agent_adl_get_failed' });
throw wrapped;
}
}
static async update(connection, libraryId, input) {
try {
return await connection.request({
method: 'PATCH',
url: baseUrl(connection, libraryId),
body: JSON.stringify(input),
headers: { 'Content-Type': 'application/json' },
});
}
catch (error) {
const wrapped = core_1.SfError.wrap(error);
await core_1.Lifecycle.getInstance().emitTelemetry({ eventName: 'agent_adl_update_failed' });
throw wrapped;
}
}
static async delete(connection, libraryId) {
try {
await connection.request({
method: 'DELETE',
url: baseUrl(connection, libraryId),
});
}
catch (error) {
const wrapped = core_1.SfError.wrap(error);
await core_1.Lifecycle.getInstance().emitTelemetry({ eventName: 'agent_adl_delete_failed' });
throw wrapped;
}
}
static async status(connection, libraryId, options) {
try {
let url = `${baseUrl(connection, libraryId)}/status`;
if (options?.includeArtifacts) {
url += '?includeArtifacts=true';
}
return await connection.request({
method: 'GET',
url,
});
}
catch (error) {
const wrapped = core_1.SfError.wrap(error);
await core_1.Lifecycle.getInstance().emitTelemetry({ eventName: 'agent_adl_status_failed' });
throw wrapped;
}
}
static async listFiles(connection, libraryId, options) {
try {
let url = `${baseUrl(connection, libraryId)}/files`;
const params = new URLSearchParams();
if (options?.pageSize !== undefined)
params.append('pageSize', String(options.pageSize));
if (options?.offset !== undefined)
params.append('offset', String(options.offset));
if (options?.sortBy)
params.append('sortBy', options.sortBy);
if (options?.sortOrder)
params.append('sortOrder', options.sortOrder);
if (options?.name)
params.append('name', options.name);
if (options?.status)
params.append('status', options.status);
const queryString = params.toString();
if (queryString) {
url += `?${queryString}`;
}
return await connection.request({
method: 'GET',
url,
});
}
catch (error) {
const wrapped = core_1.SfError.wrap(error);
await core_1.Lifecycle.getInstance().emitTelemetry({ eventName: 'agent_adl_list_files_failed' });
throw wrapped;
}
}
static async deleteFile(connection, libraryId, fileId) {
try {
await connection.request({
method: 'DELETE',
url: `${baseUrl(connection, libraryId)}/files/${fileId}`,
});
}
catch (error) {
const wrapped = core_1.SfError.wrap(error);
await core_1.Lifecycle.getInstance().emitTelemetry({ eventName: 'agent_adl_file_delete_failed' });
throw wrapped;
}
}
static async upload(connection, libraryId, filePaths, options) {
const url = baseUrl(connection, libraryId);
const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
if (paths.length === 0) {
throw new core_1.SfError('At least one file is required.', 'NoFilesProvided');
}
await AgentDataLibrary.checkUploadReadiness(connection, url);
const fileNames = paths.map((p) => ({ fileName: (0, node_path_1.basename)(p) }));
const uploadEntries = await AgentDataLibrary.getUploadUrls(connection, url, fileNames);
await Promise.all(paths.map((path, i) => AgentDataLibrary.uploadToS3(uploadEntries[i], path)));
const uploadedFiles = uploadEntries.map((entry, i) => ({
filePath: entry.filePath,
fileSize: (0, node_fs_1.statSync)(paths[i]).size,
}));
await AgentDataLibrary.triggerIndexing(connection, url, uploadedFiles);
if (options?.waitMinutes) {
const detail = await AgentDataLibrary.pollForReadiness(connection, url, libraryId, options.waitMinutes * 60);
return {
libraryId,
retrieverId: detail.retrieverId,
ragFeatureConfigId: `ARFPC_${libraryId}`,
status: 'READY',
};
}
return { libraryId, status: 'IN_PROGRESS' };
}
static async addFile(connection, libraryId, filePaths) {
const url = baseUrl(connection, libraryId);
const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
if (paths.length === 0) {
throw new core_1.SfError('At least one file is required.', 'NoFilesProvided');
}
const fileInfos = paths.map((p) => ({ fileName: (0, node_path_1.basename)(p) }));
const uploadEntries = await AgentDataLibrary.getUploadUrls(connection, url, fileInfos);
await Promise.all(paths.map((path, i) => AgentDataLibrary.uploadToS3(uploadEntries[i], path)));
const uploadedFiles = uploadEntries.map((entry, i) => ({
filePath: entry.filePath,
fileSize: (0, node_fs_1.statSync)(paths[i]).size,
}));
await connection.request({
method: 'POST',
url: `${url}/files`,
body: JSON.stringify({ uploadedFiles }),
headers: { 'Content-Type': 'application/json' },
});
await core_1.Lifecycle.getInstance().emitTelemetry({ eventName: 'agent_adl_file_add_success' });
const fileNames = paths.map((p) => (0, node_path_1.basename)(p));
const fileName = fileNames.join(', ');
return { success: true, fileName, fileNames, libraryId };
}
static async waitForReady(connection, libraryId, waitSeconds) {
return AgentDataLibrary.pollForReadiness(connection, baseUrl(connection, libraryId), libraryId, waitSeconds);
}
// ── Private helpers ───────────────────────────────────────
static async checkUploadReadiness(connection, url) {
const maxAttempts = 3;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
let readiness;
try {
// eslint-disable-next-line no-await-in-loop
readiness = await connection.request({
method: 'GET',
url: `${url}/upload-readiness?waitMaxTime=120000`,
});
}
catch (error) {
throw core_1.SfError.wrap(error);
}
if (readiness.ready)
return;
if (attempt === maxAttempts) {
throw new core_1.SfError('Library not ready for upload after waiting.', 'UploadNotReady');
}
}
}
static async getUploadUrls(connection, url, files) {
const response = await connection.request({
method: 'POST',
url: `${url}/file-upload-urls`,
body: JSON.stringify({ files }),
headers: { 'Content-Type': 'application/json' },
});
return response.uploadUrls;
}
static async uploadToS3(uploadEntry, filePath) {
const maxFileSize = 100 * 1024 * 1024; // 100 MB — SearchIndex limit for chunking/vectorization
const fileSize = (0, node_fs_1.statSync)(filePath).size;
if (fileSize > maxFileSize) {
throw new core_1.SfError(`File size (${fileSize} bytes) exceeds maximum of ${maxFileSize} bytes.`, 'FileTooLarge');
}
const fileBuffer = (0, node_fs_1.readFileSync)(filePath);
const response = await fetch(uploadEntry.uploadUrl, {
method: 'PUT',
headers: uploadEntry.headers,
body: fileBuffer,
});
if (!response.ok) {
const body = await response.text();
throw new core_1.SfError(`S3 upload failed: HTTP ${response.status}: ${body}`, 'S3UploadFailed');
}
}
static async triggerIndexing(connection, url, uploadedFiles) {
return connection.request({
method: 'POST',
url: `${url}/indexing`,
body: JSON.stringify({ uploadedFiles }),
headers: { 'Content-Type': 'application/json' },
});
}
static async pollForReadiness(connection, url, libraryId, waitSeconds) {
const deadline = Date.now() + waitSeconds * 1000;
const pollInterval = 10000;
// eslint-disable-next-line no-await-in-loop
while (Date.now() < deadline) {
// eslint-disable-next-line no-await-in-loop
const detail = await connection.request({ method: 'GET', url });
if (detail.retrieverId)
return detail;
if (detail.status === 'FAILED') {
throw new core_1.SfError('Library indexing failed.', 'IndexingFailed');
}
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => setTimeout(resolve, pollInterval));
}
throw new core_1.SfError(`Indexing did not complete within ${waitSeconds} seconds.`, 'UploadTimeout');
}
}
exports.AgentDataLibrary = AgentDataLibrary;
//# sourceMappingURL=agentDataLibrary.js.map