@future-agi/sdk
Version:
We help GenAI teams maintain high-accuracy for their Models in production.
418 lines • 16.8 kB
JavaScript
import { APIKeyAuth, ResponseHandler } from '../api/auth';
import { HttpMethod } from '../api/types';
import { Routes } from '../utils/routes';
import { SUPPORTED_FILE_TYPES } from './types';
import { SDKException, InvalidAuthError, FileNotFoundException, UnsupportedFileType } from '../utils/errors';
import * as fs from 'fs';
import * as path from 'path';
/**
* Response handler for Knowledge Base operations
*/
class KBResponseHandler extends ResponseHandler {
/**
* Parse successful knowledge base response
*/
static _parseSuccess(response) {
var _a;
const data = response.data;
const method = (_a = response.config.method) === null || _a === void 0 ? void 0 : _a.toUpperCase();
const url = response.config.url || '';
// Treat backend payloads with { status: false } as errors
if ((data === null || data === void 0 ? void 0 : data.status) === false) {
const msg = data.result || data.detail || 'Knowledge Base operation failed';
throw new SDKException(msg);
}
if (method === HttpMethod.POST && url.includes(Routes.knowledge_base)) {
return data.result;
}
if (method === HttpMethod.PATCH && url.includes(Routes.knowledge_base)) {
return data.result;
}
if (method === HttpMethod.DELETE) {
return data;
}
return data;
}
/**
* Handle knowledge base operation errors
*/
static _handleError(response) {
if (response.status === 403) {
throw new InvalidAuthError();
}
const extractMsg = () => {
var _a, _b, _c;
return (((_a = response.data) === null || _a === void 0 ? void 0 : _a.result) ||
((_b = response.data) === null || _b === void 0 ? void 0 : _b.detail) ||
((_c = response.data) === null || _c === void 0 ? void 0 : _c.message) ||
response.statusText);
};
if (response.status === 429) {
throw new SDKException(extractMsg() || 'Rate limit exceeded.');
}
if (response.status >= 400 && response.status < 500) {
throw new SDKException(extractMsg() || `Client error ${response.status}`);
}
if (response.status >= 500) {
throw new SDKException(extractMsg() || 'Server encountered an error.');
}
// Fallback: throw generic error
throw new SDKException('Knowledge base operation failed.');
}
}
/**
* Knowledge Base client for managing knowledge bases and file operations
*/
export class KnowledgeBase extends APIKeyAuth {
constructor(kbName, options = {}) {
super({
fiApiKey: options.fiApiKey,
fiSecretKey: options.fiSecretKey,
fiBaseUrl: options.fiBaseUrl,
timeout: options.timeout
});
this._validFilePaths = [];
this.kb = undefined;
if (kbName) {
// Resolve name → id at construction time
this._getKbFromName(kbName)
.then(foundKb => {
this.kb = foundKb;
})
.catch(() => {
throw new SDKException(`Knowledge Base with name '${kbName}' not found. Please create it first.`);
});
}
}
/**
* Update name of Knowledge Base and/or add files to it
*/
async updateKb({ kbName, newName, filePaths }) {
try {
// Resolve target KB by name if needed
if (!this.kb || this.kb.name !== kbName) {
this.kb = await this._getKbFromName(kbName);
}
if (filePaths) {
try {
await this._checkFilePaths(filePaths);
}
catch (error) {
if (error instanceof FileNotFoundException || error instanceof UnsupportedFileType) {
throw new SDKException("Knowledge Base update failed due to a file processing issue.", error);
}
else if (error instanceof SDKException) {
throw new SDKException("Knowledge Base update failed due to invalid file path arguments.", error);
}
else {
throw new SDKException("An unexpected error occurred during file path validation for update.", error);
}
}
}
const url = `${this.baseUrl}/${Routes.knowledge_base}`;
// Prepare form fields
const bodyFields = {
name: newName !== null && newName !== void 0 ? newName : this.kb.name,
kb_id: this.kb.id
};
// Prepare files map for multipart upload (key uniqueness: file0, file1, ...)
const files = {};
this._validFilePaths.forEach((filePath, idx) => {
const fileName = path.basename(filePath);
const fileExt = path.extname(filePath).slice(1).toLowerCase();
// Validate supported types again (defensive)
if (!SUPPORTED_FILE_TYPES.includes(fileExt)) {
throw new UnsupportedFileType(fileExt, fileName);
}
files[`file${idx}`] = fs.createReadStream(filePath);
});
const response = await this.request({
method: HttpMethod.PATCH,
url,
data: bodyFields,
files,
timeout: 300000 // 5-min timeout similar to Python
}, KBResponseHandler);
// Check if server reported any files that failed to upload
if (response.notUploaded && response.notUploaded.length > 0) {
throw new SDKException(`Server reported that some files were not uploaded successfully: ${response.notUploaded.join(', ')}`);
}
if (response && this.kb) {
if (response.id)
this.kb.id = response.id;
if (response.name)
this.kb.name = response.name;
if (response.files)
this.kb.files = response.files;
}
return this;
}
catch (error) {
if (error instanceof SDKException) {
throw error;
}
throw new SDKException("Failed to update the Knowledge Base due to an unexpected error.", error);
}
}
/**
* Delete files from the Knowledge Base
*/
async deleteFilesFromKb({ kbName, fileNames }) {
try {
if (!this.kb || this.kb.name !== kbName) {
this.kb = await this._getKbFromName(kbName);
}
if (!fileNames || fileNames.length === 0) {
throw new SDKException("Files to be deleted not found or list is empty. Please provide correct File Names.");
}
const url = `${this.baseUrl}/${Routes.knowledge_base_files}`;
const data = {
file_names: fileNames,
kb_id: this.kb.id
};
await this.request({
method: HttpMethod.DELETE,
url,
json: data,
headers: { 'Content-Type': 'application/json' }
}, KBResponseHandler);
return this;
}
catch (error) {
if (error instanceof SDKException) {
throw error;
}
throw new SDKException("Failed to delete files from the Knowledge Base due to an unexpected error.", error);
}
}
/**
* Delete a Knowledge Base
*/
async deleteKb({ kbNames, kbIds } = {}) {
var _a;
try {
const resolvedIds = [];
// Resolve names first
if (kbNames) {
const namesArr = Array.isArray(kbNames) ? kbNames : [kbNames];
for (const n of namesArr) {
try {
const conf = await this._getKbFromName(n);
resolvedIds.push(String(conf.id));
}
catch (_b) {
// ignore names that weren't found
}
}
if (resolvedIds.length === 0) {
throw new SDKException('None of the provided Knowledge Base names could be resolved.');
}
}
// Legacy ids param
if (kbIds) {
if (typeof kbIds === 'string') {
resolvedIds.push(kbIds);
}
else if (Array.isArray(kbIds)) {
resolvedIds.push(...kbIds.map(id => String(id)));
}
else {
throw new SDKException('kb_ids must be a string or a list of strings.');
}
}
// Fallback to cached KB
if (resolvedIds.length === 0 && ((_a = this.kb) === null || _a === void 0 ? void 0 : _a.id)) {
resolvedIds.push(this.kb.id);
}
if (resolvedIds.length === 0) {
throw new SDKException('No Knowledge Base specified for deletion.');
}
const url = `${this.baseUrl}/${Routes.knowledge_base}`;
const jsonPayload = { kb_ids: resolvedIds };
await this.request({
method: HttpMethod.DELETE,
url,
json: jsonPayload,
headers: { 'Content-Type': 'application/json' }
}, KBResponseHandler);
// Clear current KB if it was deleted
if (this.kb && this.kb.id && resolvedIds.includes(this.kb.id)) {
this.kb = undefined;
}
return this;
}
catch (error) {
if (error instanceof SDKException) {
throw error;
}
throw new SDKException("Failed to delete Knowledge Base(s) due to an unexpected error.", error);
}
}
/**
* Create a Knowledge Base
*/
async createKb(name, filePaths = []) {
var _a;
try {
const finalKbName = name || ((_a = this.kb) === null || _a === void 0 ? void 0 : _a.name) || "Unnamed KB";
const url = `${this.baseUrl}/${Routes.knowledge_base}`;
// Prepare fields
const bodyFields = { name: finalKbName };
// Handle optional files
const files = {};
if (filePaths) {
await this._checkFilePaths(filePaths);
this._validFilePaths.forEach((filePath, idx) => {
const fileName = path.basename(filePath);
const fileExt = path.extname(filePath).slice(1).toLowerCase();
if (!SUPPORTED_FILE_TYPES.includes(fileExt)) {
throw new UnsupportedFileType(fileExt, fileName);
}
files[`file${idx}`] = fs.createReadStream(filePath);
});
}
const response = await this.request({
method: HttpMethod.POST,
url,
data: bodyFields,
files: Object.keys(files).length > 0 ? files : undefined,
timeout: 300000
}, KBResponseHandler);
// Check if server reported any files that failed to upload during creation
if (response.notUploaded && response.notUploaded.length > 0) {
throw new SDKException(`Server reported that some files were not uploaded during Knowledge Base creation: ${response.notUploaded.join(', ')}`);
}
this.kb = {
id: response.kbId,
name: response.kbName,
files: response.fileIds || []
};
return this;
}
catch (error) {
if (error instanceof SDKException) {
throw error;
}
throw new SDKException("Failed to create the Knowledge Base due to an unexpected error.", error);
}
}
/**
* List all knowledge bases
*/
async listKbs(search) {
try {
const params = search ? { search } : {};
const response = await this.request({
method: HttpMethod.GET,
url: `${this.baseUrl}/${Routes.knowledge_base_list}`,
params
}, KBResponseHandler);
return response.result.tableData.map(item => ({
id: item.id,
name: item.name,
status: item.status,
files: item.files
}));
}
catch (error) {
if (error instanceof SDKException) {
throw error;
}
throw new SDKException("Failed to list Knowledge Bases due to an unexpected error.", error);
}
}
/**
* Get knowledge base by name
*/
async _getKbFromName(kbName) {
const response = await this.request({
method: HttpMethod.GET,
url: `${this.baseUrl}/${Routes.knowledge_base_list}`,
params: { search: kbName }
}, KBResponseHandler);
const data = response.result.tableData;
if (!data || data.length === 0) {
throw new SDKException(`Knowledge Base with name '${kbName}' not found.`);
}
return {
id: data[0].id,
name: data[0].name,
status: data[0].status,
files: data[0].files
};
}
/**
* Validate file paths
*/
async _checkFilePaths(filePaths) {
this._validFilePaths = [];
if (typeof filePaths === 'string') {
try {
const stats = await fs.promises.stat(filePaths);
if (stats.isDirectory()) {
const files = await fs.promises.readdir(filePaths);
const allFiles = [];
for (const file of files) {
const fullPath = path.join(filePaths, file);
const fileStats = await fs.promises.stat(fullPath);
if (fileStats.isFile()) {
allFiles.push(fullPath);
}
}
if (allFiles.length === 0) {
throw new FileNotFoundException(filePaths, `The directory '${filePaths}' is empty or contains no files.`);
}
this._validFilePaths = allFiles;
return true;
}
else if (stats.isFile()) {
this._validFilePaths = [filePaths];
return true;
}
else {
throw new FileNotFoundException(filePaths, `The provided path '${filePaths}' is not a valid file or directory.`);
}
}
catch (error) {
if (error instanceof FileNotFoundException) {
throw error;
}
throw new FileNotFoundException(filePaths, `The provided path '${filePaths}' does not exist or is not accessible.`);
}
}
else if (Array.isArray(filePaths)) {
if (filePaths.length === 0) {
throw new FileNotFoundException(filePaths, "The provided list of file paths is empty.");
}
const validPaths = [];
const missingFiles = [];
for (const filePath of filePaths) {
try {
const stats = await fs.promises.stat(filePath);
if (stats.isFile()) {
validPaths.push(filePath);
}
else {
missingFiles.push(filePath);
}
}
catch (_a) {
missingFiles.push(filePath);
}
}
if (missingFiles.length > 0) {
throw new FileNotFoundException(missingFiles, `Some file paths are invalid, not files, or do not exist: ${missingFiles.join(', ')}`);
}
if (validPaths.length === 0) {
throw new FileNotFoundException(filePaths, "No valid files found in the provided list.");
}
this._validFilePaths = validPaths;
return true;
}
else {
throw new SDKException(`Unsupported type for file_paths: ${typeof filePaths}. Expected string or array.`);
}
}
}
export default KnowledgeBase;
//# sourceMappingURL=client.js.map