UNPKG

@endlessriver/optimaiz

Version:

Client SDK for interacting with the Optimaiz logging & trace system.

483 lines (480 loc) 19.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.OptimaizClient = exports.OptimaizServerError = exports.OptimaizValidationError = exports.OptimaizAuthenticationError = exports.OptimaizError = void 0; const crypto_1 = __importDefault(require("crypto")); const DEFAULT_BASE_URL = "https://www.optimaiz.io"; // Custom error types for better error handling class OptimaizError extends Error { constructor(message, status, details, type) { super(message); this.name = 'OptimaizError'; this.status = status; this.details = details; this.type = type; } } exports.OptimaizError = OptimaizError; class OptimaizAuthenticationError extends OptimaizError { constructor(message, details) { super(message, 401, details, 'AUTHENTICATION_ERROR'); this.name = 'OptimaizAuthenticationError'; } } exports.OptimaizAuthenticationError = OptimaizAuthenticationError; class OptimaizValidationError extends OptimaizError { constructor(message, details) { super(message, 400, details, 'VALIDATION_ERROR'); this.name = 'OptimaizValidationError'; } } exports.OptimaizValidationError = OptimaizValidationError; class OptimaizServerError extends OptimaizError { constructor(message, details) { super(message, 500, details, 'SERVER_ERROR'); this.name = 'OptimaizServerError'; } } exports.OptimaizServerError = OptimaizServerError; // Tool helper functions function validateToolDefinitions(tools) { const errors = []; for (const tool of tools) { if (!tool.name || typeof tool.name !== 'string') { errors.push(`Tool must have a valid name: ${tool.name}`); } if (!tool.description || typeof tool.description !== 'string') { errors.push(`Tool ${tool.name} must have a valid description`); } if (!tool.parameters || typeof tool.parameters !== 'object') { errors.push(`Tool ${tool.name} must have valid parameters`); } else { if (tool.parameters.type !== 'object') { errors.push(`Tool ${tool.name} parameters must have type 'object'`); } if (!tool.parameters.properties || typeof tool.parameters.properties !== 'object') { errors.push(`Tool ${tool.name} must have properties defined`); } } } return { valid: errors.length === 0, errors }; } function convertProviderToolsToUnified(tools, provider) { switch (provider.toLowerCase()) { case 'openai': return tools.map((tool) => { if (!tool.function || !tool.function.name || !tool.function.description) { throw new Error('Invalid OpenAI tool format: missing function or required fields'); } return { name: tool.function.name, description: tool.function.description, parameters: tool.function.parameters, }; }); case 'anthropic': return tools.map((tool) => { if (!tool.name || !tool.description || !tool.input_schema) { throw new Error('Invalid Anthropic tool format: missing required fields'); } return { name: tool.name, description: tool.description, parameters: { type: 'object', properties: tool.input_schema.properties, required: tool.input_schema.required, }, }; }); case 'mistral': return tools.map((tool) => { if (!tool.function || !tool.function.name || !tool.function.description) { throw new Error('Invalid Mistral tool format: missing function or required fields'); } return { name: tool.function.name, description: tool.function.description, parameters: tool.function.parameters, }; }); default: // Assume already in unified format return tools; } } function getToolsInProviderFormat(tools, provider) { switch (provider.toLowerCase()) { case 'openai': return tools.map(tool => ({ type: 'function', function: { name: tool.name, description: tool.description, parameters: tool.parameters, }, })); case 'anthropic': return tools.map(tool => ({ name: tool.name, description: tool.description, input_schema: { type: 'object', properties: tool.parameters.properties, required: tool.parameters.required, }, })); case 'mistral': return tools.map(tool => ({ type: 'function', function: { name: tool.name, description: tool.description, parameters: tool.parameters, }, })); default: // Return in unified format return tools; } } class OptimaizClient { constructor(config) { this.config = config; if (!config.token || typeof config.token !== 'string') { throw new Error('OptimaizClient requires a valid token'); } this.baseUrl = config.baseUrl || DEFAULT_BASE_URL; } async post(path, body) { try { const res = await fetch(`${this.baseUrl}${path}`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${this.config.token}`, }, body: JSON.stringify({ ...body }), }); /* if (!res.ok) { let errorMessage: string; let errorDetails: string | undefined; try { const errorData = await res.json(); errorMessage = errorData.error || `HTTP ${res.status}`; errorDetails = errorData.details; } catch { // Fallback to text if JSON parsing fails const errorText = await res.text(); errorMessage = errorText || `HTTP ${res.status}`; } // Create specific error types based on status codes if (res.status === 401) { throw new OptimaizAuthenticationError(`Authentication failed: ${errorMessage}`, errorDetails); } else if (res.status === 403) { const authError = new OptimaizError(`Access forbidden: ${errorMessage}`, 403, errorDetails, 'AUTHORIZATION_ERROR'); authError.name = 'OptimaizAuthorizationError'; throw authError; } else if (res.status === 400) { throw new OptimaizValidationError(`Validation error: ${errorMessage}`, errorDetails); } else if (res.status === 500) { throw new OptimaizServerError(`Server error: ${errorMessage}`, errorDetails); } else { const error = new OptimaizError(errorMessage, res.status, errorDetails); throw error; } } */ return res.json(); } catch (error) { if (error.name === 'TypeError' && error.message.includes('fetch')) { const networkError = new Error(`Network error: Unable to connect to ${this.baseUrl}. Please check your internet connection.`); networkError.type = 'NETWORK_ERROR'; throw networkError; } throw error; } } async startTrace(data) { // Client-side validation matching server patterns if (!data.promptTemplate && !data.promptVariables) { throw new Error("No prompts provided: either promptTemplate or promptVariables must be provided"); } if (data.tools && !data.provider) { throw new Error("Tool definitions provided but no provider specified for conversion"); } if (data.tools) { const validation = this.validateTools(data.tools); if (!validation.valid) { throw new Error(`Invalid tool definitions: ${validation.errors.join(', ')}`); } } return this.post("/api/v1/interactions/start", data); } async appendResponse(data) { if (!data.traceId) { throw new Error("Missing traceId"); } return this.post("/api/v1/interactions/append", data); } async finalizeTrace(traceId) { if (!traceId) { throw new Error("Missing traceId"); } return this.post("/api/v1/interactions/finalize", { traceId }); } async logError(traceId, error) { if (!traceId || !error) { throw new Error("Missing traceId or error"); } return this.post("/api/v1/interactions/error", { traceId, error }); } async sendFeedback(traceId, feedback) { if (!traceId || !feedback) { throw new Error("Missing traceId or feedback"); } return this.post("/api/v1/interactions/feedback", { traceId, feedback }); } async addToolExecution(data) { if (!data.traceId) { throw new Error("Missing traceId"); } if (!data.toolId || !data.toolName) { throw new Error("Missing toolId or toolName"); } return this.post("/api/v1/interactions/tool-execution", data); } async addToolResults(data) { if (!data.traceId) { throw new Error("Missing traceId"); } if (!Array.isArray(data.toolResults) || data.toolResults.length === 0) { throw new Error("Missing or invalid toolResults array"); } return this.post("/api/v1/interactions/tool-results", data); } composePrompts(templates, variables) { if (!Array.isArray(templates)) { throw new Error('Templates must be an array'); } if (!variables || typeof variables !== 'object') { throw new Error('Variables must be an object'); } const prompts = []; const promptTemplate = []; for (const { role, content, type = "text" } of templates) { const resolved = Object.entries(variables).reduce((acc, [k, v]) => acc.replace(`{${k}}`, String(v || '')), content); prompts.push({ type, role, value: resolved }); promptTemplate.push({ type, role, value: content }); } return { prompts, promptTemplate, promptVariables: variables }; } async wrapLLMCall({ traceId = crypto_1.default.randomUUID(), agentId, flowId, threadId, sessionId, userId, promptTemplate, promptVariables, tools, provider, model, modelParams, metadata, tags, call, }) { try { await this.startTrace({ traceId, agentId, flowId, threadId, sessionId, userId, promptTemplate, promptVariables, tools, provider, model, modelParams, metadata, tags, }); } catch (err) { console.warn("[Optimaiz] Failed to start trace", err); } try { const response = await call(); try { await this.appendResponse({ traceId, rawResponse: response, provider, model }); } catch (err) { console.warn("[Optimaiz] Failed to append response", err); } try { await this.finalizeTrace(traceId); } catch (err) { console.warn("[Optimaiz] Failed to finalize trace", err); } return { response, traceId }; } catch (err) { try { await this.logError(traceId, { message: err.message, code: err.code || "unknown_error", details: err.stack || err, }); } catch (logErr) { console.warn("[Optimaiz] Failed to log error:", logErr); } console.error("[Optimaiz] Error in wrapLLMCall", err); throw err; } } generatePromptFromTools({ toolInfo, toolInput, }) { if (!Array.isArray(toolInfo)) { throw new Error('ToolInfo must be an array'); } if (!toolInput || typeof toolInput !== 'object') { throw new Error('ToolInput must be an object'); } if (!toolInput.name || typeof toolInput.name !== 'string') { throw new Error('ToolInput must have a valid name'); } const fn = toolInfo.find(f => f.name === toolInput.name); if (!fn) throw new Error(`Tool ${toolInput.name} not found in toolInfo`); // Use the properly typed properties from StandardToolDefinition const properties = fn.parameters.properties; const arguments_ = toolInput.arguments || {}; const argKeys = Object.keys(arguments_); const promptText = [ `Use the tool: {toolName}.`, fn.description ? `Tool Description: {description}.` : "", ...argKeys.map(key => `${key}: {${key}}${properties[key]?.description ? ` // ${properties[key].description}` : ""}`), ] .filter(Boolean) .join("\n"); const promptTemplate = [ { role: "user", type: "text", value: promptText, }, ]; const promptVariables = { toolName: fn.name, description: fn.description || "", ...Object.fromEntries(argKeys.map(k => [k, String(arguments_[k] || '')])), }; return { promptTemplate, promptVariables }; } async call(data) { if (!data || typeof data !== 'object') { throw new Error('Call data must be an object'); } if (!data.promptVariables || typeof data.promptVariables !== 'object') { throw new Error('promptVariables must be provided and must be an object'); } data.traceId = data.traceId || crypto_1.default.randomUUID(); try { if (!data.promptTemplate && !data.agentId) { throw new Error("Either promptTemplate or agentId must be provided"); } const { data: response, error } = await this.post("/api/v1/call", data); return { data: response, error }; } catch (err) { if (data.traceId) { try { await this.logError(data.traceId, { message: err.message, code: err.code || "unknown_error", details: err.stack || err, }); } catch (logErr) { console.warn("[Optimaiz] Failed to log error:", logErr); } } return { data: null, error: err.message }; } } // Tool conversion methods convertToolsToProviderFormat(tools, provider) { if (!Array.isArray(tools)) { throw new Error('Tools must be an array'); } // First convert to unified format const unifiedTools = tools.map(tool => ({ name: tool.name, description: tool.description, parameters: tool.parameters, category: tool.category, tags: tool.tags, version: tool.version, deprecated: tool.deprecated, })); // Then convert to provider-specific format return getToolsInProviderFormat(unifiedTools, provider); } convertProviderToolsToStandard(tools, provider) { if (!Array.isArray(tools)) { throw new Error('Tools must be an array'); } const unifiedTools = convertProviderToolsToUnified(tools, provider); return unifiedTools.map(tool => ({ name: tool.name, description: tool.description, parameters: tool.parameters, category: tool.category, tags: tool.tags, version: tool.version, deprecated: tool.deprecated, })); } validateTools(tools) { if (!Array.isArray(tools)) { throw new Error('Tools must be an array'); } const unifiedTools = tools.map(tool => ({ name: tool.name, description: tool.description, parameters: tool.parameters, category: tool.category, tags: tool.tags, version: tool.version, deprecated: tool.deprecated, })); return validateToolDefinitions(unifiedTools); } /** * Utility method to handle errors with proper error types */ static handleError(error) { if (error instanceof OptimaizError) { throw error; } // Convert generic errors to OptimaizError if (error.message && typeof error.message === 'string') { if (error.message.includes('Authentication failed') || error.message.includes('401')) { throw new OptimaizAuthenticationError(error.message); } else if (error.message.includes('Validation error') || error.message.includes('400')) { throw new OptimaizValidationError(error.message); } else if (error.message.includes('Server error') || error.message.includes('500')) { throw new OptimaizServerError(error.message); } } // Default to generic OptimaizError throw new OptimaizError(error.message || 'Unknown error occurred'); } /** * Check if an error is a specific type */ static isAuthenticationError(error) { return error instanceof OptimaizAuthenticationError || error.type === 'AUTHENTICATION_ERROR'; } static isValidationError(error) { return error instanceof OptimaizValidationError || error.type === 'VALIDATION_ERROR'; } static isServerError(error) { return error instanceof OptimaizServerError || error.type === 'SERVER_ERROR'; } } exports.OptimaizClient = OptimaizClient;