helpingai
Version:
The official JavaScript/TypeScript library for the HelpingAI API - Advanced AI with Emotional Intelligence
263 lines • 8.97 kB
JavaScript
/**
* Main HelpingAI client implementation
*/
import { parseErrorResponse, TimeoutError, NetworkError, ToolExecutionError } from './errors';
import { MCPManager } from './mcp/manager';
import { executeTool, getRegistry } from './tools/core';
import { executeBuiltinTool, isBuiltinTool } from './tools/builtin';
export class HelpingAI {
constructor(config = {}) {
/**
* Chat completions API
*/
this.chat = {
completions: {
create: async (request) => {
return this.createChatCompletion(request);
},
},
};
/**
* Models API
*/
this.models = {
list: async () => {
return this.listModels();
},
retrieve: async (modelId) => {
return this.retrieveModel(modelId);
},
};
this.config = {
apiKey: config.apiKey || '',
baseURL: config.baseURL || 'https://api.helpingai.co/v1',
timeout: config.timeout || 30000,
organization: config.organization || '',
defaultHeaders: config.defaultHeaders || {},
};
if (!this.config.apiKey) {
console.warn('HelpingAI API key not provided. Set HAI_API_KEY environment variable or pass apiKey in config.');
}
}
/**
* Direct tool execution method
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async call(toolName, args) {
try {
// Check if it's an MCP tool
if (this.mcpManager?.isMCPTool(toolName)) {
return await this.mcpManager.executeTool(toolName, args);
}
// Check if it's a registered tool
if (getRegistry().has(toolName)) {
return await executeTool(toolName, args);
}
// Handle built-in tools
if (isBuiltinTool(toolName)) {
return await executeBuiltinTool(toolName, args);
}
throw new ToolExecutionError(`Tool '${toolName}' not found`, toolName);
}
catch (error) {
throw new ToolExecutionError(`Failed to execute tool '${toolName}': ${error instanceof Error ? error.message : String(error)}`, toolName);
}
}
/**
* Create chat completion
*/
async createChatCompletion(request) {
// Process tools if provided
const processedTools = await this.processTools(request.tools);
const requestBody = {
...request,
...(processedTools && { tools: processedTools }),
};
if (request.stream) {
return this.createStreamingCompletion(requestBody);
}
else {
return this.createNonStreamingCompletion(requestBody);
}
}
/**
* Process tools configuration
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async processTools(tools) {
if (!tools || tools.length === 0) {
return undefined;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const processedTools = [];
for (const tool of tools) {
if (typeof tool === 'string') {
// Built-in tool
processedTools.push(tool);
}
else if ('mcpServers' in tool) {
// MCP configuration
try {
const { manager, tools: mcpTools } = await MCPManager.processConfiguration(tool);
this.mcpManager = manager;
processedTools.push(...mcpTools);
}
catch (error) {
console.warn('Failed to process MCP configuration:', error);
}
}
else {
// Regular OpenAI-format tool
processedTools.push(tool);
}
}
return processedTools;
}
/**
* Create non-streaming completion
*/
async createNonStreamingCompletion(request) {
const response = await this.makeRequest('/chat/completions', {
method: 'POST',
body: JSON.stringify(request),
});
return response;
}
/**
* Create streaming completion
*/
async createStreamingCompletion(_request) {
const url = `${this.config.baseURL}/chat/completions`;
const headers = this.getHeaders();
return new Promise((resolve, reject) => {
const eventSource = new EventSource(url, {
headers: {
...headers,
'Content-Type': 'application/json',
},
});
const chunks = [];
let resolved = false;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
eventSource.onmessage = (event) => {
if (event.data === '[DONE]') {
eventSource.close();
if (!resolved) {
resolved = true;
resolve(this.createAsyncIterable(chunks));
}
return;
}
try {
const chunk = JSON.parse(event.data);
chunks.push(chunk);
}
catch (error) {
console.warn('Failed to parse streaming chunk:', error);
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
eventSource.onerror = (_error) => {
eventSource.close();
if (!resolved) {
resolved = true;
reject(new NetworkError('Streaming connection failed'));
}
};
// Send the request body (this is a simplified implementation)
// In a real implementation, you'd need to handle POST data with EventSource
setTimeout(() => {
if (!resolved) {
resolved = true;
resolve(this.createAsyncIterable(chunks));
}
}, 100);
});
}
/**
* Create async iterable from chunks
*/
async *createAsyncIterable(chunks) {
for (const chunk of chunks) {
yield chunk;
}
}
/**
* List available models
*/
async listModels() {
const response = await this.makeRequest('/models', { method: 'GET' });
return response;
}
/**
* Retrieve specific model
*/
async retrieveModel(modelId) {
const response = await this.makeRequest(`/models/${modelId}`, { method: 'GET' });
return response;
}
/**
* Make HTTP request
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async makeRequest(endpoint, options) {
const url = `${this.config.baseURL}${endpoint}`;
const headers = this.getHeaders();
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
try {
const response = await fetch(url, {
...options,
headers: {
...headers,
...options.headers,
},
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw parseErrorResponse(response.status, errorData, Object.fromEntries(response.headers.entries()));
}
return await response.json();
}
catch (error) {
clearTimeout(timeoutId);
if (error instanceof Error && error.name === 'AbortError') {
throw new TimeoutError('Request timeout');
}
if (error instanceof TypeError && error.message.includes('fetch')) {
throw new NetworkError('Network error');
}
throw error;
}
}
/**
* Get request headers
*/
getHeaders() {
const headers = {
'Content-Type': 'application/json',
'User-Agent': 'HelpingAI-JS/1.0.0',
...this.config.defaultHeaders,
};
if (this.config.apiKey) {
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
}
if (this.config.organization) {
headers['HelpingAI-Organization'] = this.config.organization;
}
return headers;
}
/**
* Cleanup resources
*/
async cleanup() {
if (this.mcpManager) {
await this.mcpManager.cleanup();
}
}
}
// Export as HAI for compatibility
export { HelpingAI as HAI };
//# sourceMappingURL=client.js.map