Skip to main content
Glama
helpers.ts38.1 kB
// Use the global fetch available in Node.js 18+ const fetch = globalThis.fetch; /** * Configuration for Eliza API integration */ export interface ElizaConfig { baseUrl: string; timeout: number; retries: number; } /** * Response from Eliza AI agent */ export interface ElizaResponse { success: boolean; message: string; data?: any; error?: string; } /** * Test result validation */ export interface TestResult { passed: boolean; message: string; data?: any; error?: string; } /** * Default Eliza configuration */ export const DEFAULT_ELIZA_CONFIG: ElizaConfig = { baseUrl: process.env.ELIZA_API_URL || 'http://localhost:3001', timeout: 30000, retries: 3 }; /** * HTTP client for making requests to Eliza AI agent * @deprecated Use ElizaClient from './eliza-client' instead for better functionality */ export class ElizaHttpClient { private config: ElizaConfig; constructor(config: ElizaConfig = DEFAULT_ELIZA_CONFIG) { this.config = config; } // find agent async getAgents(): Promise<any> { const url = `${this.config.baseUrl}/api/agents`; const response = await fetch(url); const parsedResponse = await response.json(); const agents = parsedResponse.data.agents; try { return agents; } catch (error) { console.error(`Error getting agents: ${error}`); throw error; } } // get agent channel async getAgentChannel(): Promise<any> { // First get the C3PO agent const agent = await this.getC3POAgent(); if (!agent || !agent.id) { throw new Error('C3PO agent not found'); } const url = `${this.config.baseUrl}/api/messaging/dm-channel`; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ userId1: agent.id }), }); const channel = await response.json(); return channel; } // agent to use for E2E testing is C3PO async getC3POAgent(): Promise<any> { const agents = await this.getAgents(); try { const c3poAgent = agents.find((agent: any) => agent.name === 'C3PO'); return c3poAgent; } catch (error) { console.error(`Error finding C3PO agent: ${error}`); throw error; } } /** * Send a message to the Eliza AI agent via Discord-style external messaging * @deprecated Use ElizaClient.sendMessage() instead for better functionality */ async sendMessage(message: string): Promise<ElizaResponse> { const url = `${this.config.baseUrl}/api/messaging/external-messages`; try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.config.timeout); // Get the agent channel dynamically const channel = await this.getAgentChannel(); const channelId = channel?.id || channel?.channelId || 'test-channel'; const payload = { platform: 'discord', messageId: `test-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, channelId: channelId, userId: 'test-user', content: message, attachments: [], metadata: {} }; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(payload), signal: controller.signal, }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json() as any; return { success: true, message: data.message || data.response || data.text || data.content || '', data: data }; } catch (error) { return { success: false, message: '', error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Send a message with retries * @deprecated Use ElizaClient.sendMessageWithRetry() instead for better functionality */ async sendMessageWithRetry(message: string): Promise<ElizaResponse> { let lastError: string | undefined; for (let attempt = 1; attempt <= this.config.retries; attempt++) { const response = await this.sendMessage(message); if (response.success) { return response; } lastError = response.error; if (attempt < this.config.retries) { // Wait before retry with exponential backoff await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000)); } } return { success: false, message: '', error: `Failed after ${this.config.retries} attempts. Last error: ${lastError}` }; } } /** * Utility functions for test validation */ export class TestValidator { /** * Check if response contains success indicators */ static hasSuccessIndicators(response: string): boolean { const successPatterns = [ /success/i, /completed/i, /confirmed/i, /ready/i, /available/i, /found/i, /received/i, /verified/i, /registered/i, /logged in/i, /connected/i, /ok/i, /working/i, /active/i, /synced/i, /synchronized/i, /processed/i, /executed/i, /sent/i, /initiated/i ]; return successPatterns.some(pattern => pattern.test(response)); } /** * Check if response contains error indicators */ static hasErrorIndicators(response: string): boolean { const errorPatterns = [ /error/i, /failed/i, /not found/i, /not ready/i, /not available/i, /not connected/i, /not logged in/i, /not registered/i, /insufficient/i, /invalid/i, /timeout/i, /unavailable/i, /denied/i, /rejected/i, /blocked/i, /unable/i, /cannot/i, /could not/i, /does not exist/i, /doesn't exist/i, /no such/i, /missing/i, /absent/i, /empty/i, /null/i, /undefined/i ]; return errorPatterns.some(pattern => pattern.test(response)); } /** * Check if response contains wallet-related information */ static hasWalletStatusInfo(response: string): boolean { const walletPatterns = [ /wallet/i, /address/i, /balance/i, /transaction/i, /funds/i, /midnight/i, /blockchain/i, /mn_shield-addr/i, /shielded/i, /utxo/i, /unspent/i, /spent/i, /dust/i, /mid/i, /tokens/i, /assets/i, /sync/i, /synchronization/i, /network/i, /node/i, /indexer/i ]; return walletPatterns.some(pattern => pattern.test(response)); } /** * Check if response contains marketplace-related information */ static hasMarketplaceInfo(response: string): boolean { const marketplacePatterns = [ /marketplace/i, /service/i, /register/i, /hire/i, /content/i, /available/i ]; return marketplacePatterns.some(pattern => pattern.test(response)); } /** * Check if response indicates user is authenticated */ static hasAuthenticationSuccess(response: string): boolean { // First check for explicit "not authenticated" patterns and exclude them if (/not authenticated/i.test(response) || /not logged in/i.test(response)) { console.log('DEBUG: hasAuthenticationSuccess - Found "not authenticated" pattern, returning false'); return false; } const authSuccessPatterns = [ /authenticated/i, /logged in/i, /login successful/i, /successfully logged/i, /authentication successful/i, /you are logged in/i, /you're logged in/i, /login status: true/i, /authentication status: true/i, /ready.*marketplace/i, /access.*granted/i, /authentication.*successful/i, /login.*successful/i, /successfully.*authenticated/i, /successfully.*logged/i ]; const result = authSuccessPatterns.some(pattern => pattern.test(response)); console.log('DEBUG: hasAuthenticationSuccess - Input:', response.substring(0, 100) + '...', 'Result:', result); return result; } /** * Check if response indicates user is NOT authenticated */ static hasAuthenticationFailure(response: string): boolean { const authFailurePatterns = [ /not authenticated/i, /not logged in/i, /authentication failed/i, /login failed/i, /need to log in/i, /need to authenticate/i, /please log in/i, /please authenticate/i, /you need to log in/i, /you need to authenticate/i, /authentication required/i, /login required/i, /not authenticated yet/i, /not logged in yet/i, /authentication status shows.*not authenticated/i ]; return authFailurePatterns.some(pattern => pattern.test(response)); } /** * Check if response indicates authentication is required for an action */ static hasAuthenticationRequired(response: string): boolean { const authRequiredPatterns = [ /need to log in first/i, /need to authenticate first/i, /please log in first/i, /please authenticate first/i, /authentication required/i, /login required/i, /must be logged in/i, /must be authenticated/i, /need to log in to/i, /need to authenticate to/i, /can't.*because.*not authenticated/i, /can't.*because.*not logged in/i, /unable.*because.*not authenticated/i, /unable.*because.*not logged in/i ]; return authRequiredPatterns.some(pattern => pattern.test(response)); } /** * Extract wallet address from response */ static extractWalletAddress(response: string): string | null { // Look for patterns like "address: mn_shield-addr_...", "wallet address: mn_shield-addr_...", etc. // Support both Midnight shielded addresses and traditional hex addresses // Also support markdown bold formatting with ** const addressPatterns = [ // Match "Your Midnight wallet address is **mn_shield-addr_...**" /\*\*(mn_shield-addr_[a-zA-Z0-9_]+)\*\*/i, /\*\*(0x[a-fA-F0-9]{40,})\*\*/i, // Match "address: mn_shield-addr_..." or "wallet address: mn_shield-addr_..." /(?:address|wallet address)[:\s]+(mn_shield-addr_[a-zA-Z0-9_]+)/i, /(?:address|wallet address)[:\s]+(0x[a-fA-F0-9]{40,})/i, // Generic patterns for any address format /(mn_shield-addr_[a-zA-Z0-9_]+)/i, /(0x[a-fA-F0-9]{40,})/i ]; for (const pattern of addressPatterns) { const match = response.match(pattern); if (match) { return match[1]; } } return null; } /** * Extract balance amount from response */ static extractBalance(response: string): string | null { // Look for patterns like "balance: 100", "balance is 100", etc. // Support various formats including dust units, MID, etc. const balancePatterns = [ // Match "Your current wallet balance is **51.535228**" /(?:balance|amount)[:\s]+([0-9]+(?:\.[0-9]+)?)/i, /(?:balance|amount)[:\s]+([0-9,]+(?:\.[0-9]+)?)/i, /(?:balance|amount)[:\s]+([0-9]+(?:\.[0-9]+)?)\s*(?:dust|mid|tokens?)/i, /(?:balance|amount)[:\s]+([0-9]+(?:\.[0-9]+)?)\s*(?:units?)/i, /([0-9]+(?:\.[0-9]+)?)\s*(?:dust|mid|tokens?)/i, /([0-9,]+(?:\.[0-9]+)?)\s*(?:dust|mid|tokens?)/i, // New patterns for the actual response format /(?:wallet\s+)?balance\s+is\s+\*\*([0-9]+(?:\.[0-9]+)?)\*\*/i, /(?:current\s+)?(?:wallet\s+)?balance\s+is\s+([0-9]+(?:\.[0-9]+)?)/i, /(?:balance\s+of\s+)([0-9]+(?:\.[0-9]+)?)/i, /(?:balance\s+is\s+)([0-9]+(?:\.[0-9]+)?)/i, // Match numbers with asterisks (markdown bold format) /\*\*([0-9]+(?:\.[0-9]+)?)\*\*/i, // Generic number patterns that might be balance /([0-9]+(?:\.[0-9]+)?)\s*(?:dust|mid|tokens?|units?)/i, // Fallback: any number that looks like a balance (with decimal places) /([0-9]+\.[0-9]{1,6})/i ]; for (const pattern of balancePatterns) { const match = response.match(pattern); if (match) { return match[1].replace(/,/g, ''); // Remove commas from numbers } } return null; } /** * Extract transaction ID from response */ static extractTransactionId(response: string): string | null { // Look for patterns like "transaction: abc123", "tx: abc123", etc. // Support various transaction ID formats const txPatterns = [ /(?:transaction|tx)[:\s]+([a-zA-Z0-9]{10,})/i, /(?:transaction|tx)[:\s]+([a-zA-Z0-9_-]{10,})/i, /(?:transaction|tx)[:\s]+([a-fA-F0-9]{32,})/i, /(?:transaction|tx)[:\s]+([a-zA-Z0-9]{8,})/i, /([a-zA-Z0-9]{10,})/i, // Generic pattern for any alphanumeric string /([a-zA-Z0-9_-]{10,})/i, // With underscores and hyphens /([a-fA-F0-9]{32,})/i, // Hex format /([a-zA-Z0-9]{8,})/i // Shorter IDs ]; for (const pattern of txPatterns) { const match = response.match(pattern); if (match) { return match[1]; } } return null; } /** * Validate if a string is a Midnight shielded address */ static isValidMidnightAddress(address: string): boolean { // Check for Midnight shielded address format const midnightPattern = /^mn_shield-addr_[a-zA-Z0-9_]+$/; return midnightPattern.test(address); } /** * Validate if a string is a traditional hex address */ static isValidHexAddress(address: string): boolean { // Check for traditional hex address format const hexPattern = /^0x[a-fA-F0-9]{40}$/; return hexPattern.test(address); } /** * Check if response contains a valid address (either Midnight or hex) */ static hasValidAddress(response: string): boolean { const address = this.extractWalletAddress(response); if (!address) return false; return this.isValidMidnightAddress(address) || this.isValidHexAddress(address); } /** * Create a content validator that checks for wallet information */ static createWalletInfoValidator(): (content: string) => boolean { return (content: string) => this.hasWalletStatusInfo(content); } /** * Create a content validator that checks for marketplace information */ static createMarketplaceInfoValidator(): (content: string) => boolean { return (content: string) => this.hasMarketplaceInfo(content); } /** * Create a content validator that checks for success indicators */ static createSuccessValidator(): (content: string) => boolean { return (content: string) => this.hasSuccessIndicators(content); } /** * Create a content validator that checks for error indicators */ static createErrorValidator(): (content: string) => boolean { return (content: string) => this.hasErrorIndicators(content); } /** * Create a content validator that checks for a specific keyword or phrase */ static createKeywordValidator(keyword: string, caseSensitive: boolean = false): (content: string) => boolean { return (content: string) => { if (caseSensitive) { return content.includes(keyword); } return content.toLowerCase().includes(keyword.toLowerCase()); }; } /** * Create a content validator that checks for wallet address */ static createWalletAddressValidator(): (content: string) => boolean { return (content: string) => this.hasValidAddress(content); } /** * Create a content validator that checks for balance information */ static createBalanceValidator(): (content: string) => boolean { return (content: string) => { // Check if we can extract a specific balance amount const balance = this.extractBalance(content); const hasBalanceAmount = balance !== null; // Check for balance-related keywords const balanceKeywords = ['balance', 'amount', 'available', 'current', 'wallet']; const hasBalanceKeywords = balanceKeywords.some(keyword => content.toLowerCase().includes(keyword.toLowerCase()) ); // Check if the content contains any numbers (including decimals) const hasNumbers = /\d/.test(content); // Consider it valid if we have either a specific balance amount, balance keywords, or numbers const isValid = hasBalanceAmount || hasBalanceKeywords || hasNumbers; // Add some debugging for balance validation if (!isValid) { console.log('Balance validation failed for content:', content.substring(0, 200) + '...'); } else { console.log('Balance validation succeeded:', { hasBalanceAmount, hasBalanceKeywords, hasNumbers, balance: balance || 'N/A' }); } return isValid; }; } /** * Create a content validator that checks for balance-related keywords * This is more lenient and checks for balance-related terms even if exact amount extraction fails */ static createBalanceKeywordValidator(): (content: string) => boolean { return (content: string) => { const balanceKeywords = ['balance', 'amount', 'available', 'current', 'wallet']; const hasBalanceKeywords = balanceKeywords.some(keyword => content.toLowerCase().includes(keyword.toLowerCase()) ); // Also check if we can extract a balance amount const balance = this.extractBalance(content); const hasBalanceAmount = balance !== null; const isValid = hasBalanceKeywords || hasBalanceAmount; if (!isValid) { console.log('Balance keyword validation failed for content:', content.substring(0, 200) + '...'); } else { console.log('Balance keyword validation succeeded, keywords found:', hasBalanceKeywords, 'amount found:', hasBalanceAmount); } return isValid; }; } /** * Create a simple validator that checks if the response contains any numbers * This is the most reliable approach for balance responses */ static createNumberValidator(): (content: string) => boolean { return (content: string) => { // Check if the content contains any numbers (including decimals) const hasNumbers = /\d/.test(content); if (!hasNumbers) { console.log('Number validation failed for content:', content.substring(0, 200) + '...'); } else { console.log('Number validation succeeded, found numbers in response'); } return hasNumbers; }; } /** * Create a validator that checks for authentication success */ static createAuthenticationSuccessValidator(): (content: string) => boolean { return (content: string) => this.hasAuthenticationSuccess(content); } /** * Create a validator that checks for authentication failure (not authenticated) */ static createAuthenticationFailureValidator(): (content: string) => boolean { return (content: string) => this.hasAuthenticationFailure(content); } /** * Create a validator that checks for authentication required messages */ static createAuthenticationRequiredValidator(): (content: string) => boolean { return (content: string) => this.hasAuthenticationRequired(content); } /** * Create a validator that checks for either authentication success or proper failure indication * Excludes processing messages like "please wait" or "hold on" */ static createAuthenticationStatusValidator(): (content: string) => boolean { return (content: string) => { // Check for processing messages and exclude them // Only consider it a processing message if it contains processing words but NOT authentication-related context const hasProcessingWords = /please wait|hold on|checking|verifying|moment/i.test(content); const hasAuthContext = /login status|authentication status|marketplace|check your login|verify.*information/i.test(content); const hasProcessingMessage = hasProcessingWords && !hasAuthContext; if (hasProcessingMessage) { return false; } const isAuthenticated = this.hasAuthenticationSuccess(content); const isNotAuthenticated = this.hasAuthenticationFailure(content); const requiresAuth = this.hasAuthenticationRequired(content); // Valid if we get a clear authentication status (either success or failure) const isValid = isAuthenticated || isNotAuthenticated || requiresAuth; if (!isValid) { console.log('Authentication status validation failed for content:', content.substring(0, 200) + '...'); } else { console.log('content', content); console.log('Authentication status validation succeeded:', { isAuthenticated, isNotAuthenticated, requiresAuth }); } return isValid; }; } /** * Create a validator that checks for transaction verification responses * This handles both successful verification and "not found" responses */ static createTransactionVerificationValidator(): (content: string) => boolean { return (content: string) => { // Check for transaction verification patterns const verificationPatterns = [ /transaction.*verified/i, /transaction.*found/i, /transaction.*received/i, /transaction.*exists/i, /transaction.*not found/i, /transaction.*not received/i, /transaction.*does not exist/i, /transaction.*doesn't exist/i, /no such transaction/i, /transaction.*invalid/i, /transaction.*failed/i, /unable to verify/i, /could not verify/i, /verification.*failed/i, /verification.*error/i ]; const hasVerificationPattern = verificationPatterns.some(pattern => pattern.test(content)); // Also check for error indicators that are common in transaction verification const hasErrorIndicators = this.hasErrorIndicators(content); // Check for "please wait" or "hold on" messages that indicate processing const hasProcessingMessage = /please wait|hold on|checking|verifying/i.test(content); // Valid if we have any transaction-related response (but NOT processing messages) const isValid = hasVerificationPattern || hasErrorIndicators; return isValid; }; } /** * Create a validator that checks for marketplace services list responses * Rejects "please wait" messages and accepts both empty list responses and responses with service counts/descriptions */ static createMarketplaceServicesListValidator(): (content: string) => boolean { return (content: string) => { // Reject processing messages like "please wait" or "hold on" // These messages indicate the AI is still working and haven't provided the actual result yet // Use more specific patterns to avoid false positives with words like "moment" in valid responses const hasProcessingWords = /please wait|hold on|checking|verifying|gathering|retrieving|working on|processing/i.test(content); const hasProcessingMessage = hasProcessingWords; if (hasProcessingMessage) { console.log('Marketplace services list validation: Rejecting processing message'); return false; } // Use the helper methods to check for acceptable patterns const hasNoServicesPattern = this.hasNoServicesPattern(content); const hasServiceCount = this.hasServiceCountPattern(content); const hasServiceDescription = this.hasServiceDescriptionPattern(content); const hasServiceList = this.hasServiceListPattern(content); // Valid if we have any of the acceptable patterns const isValid = hasNoServicesPattern || hasServiceCount || hasServiceDescription || hasServiceList; if (!isValid) { console.log('Marketplace services list validation failed for content:', content.substring(0, 200) + '...'); } else { console.log('Marketplace services list validation succeeded:', { hasNoServicesPattern, hasServiceCount, hasServiceDescription, hasServiceList, }); } return isValid; }; } /** * Check if response indicates no services are available in marketplace */ static hasNoServicesPattern(content: string): boolean { return /no services available|no services.*available|currently no services|no services.*registered|all services.*inactive|there are currently no services|no services.*marketplace|no offerings|no services.*moment/i.test(content); } /** * Check if response indicates services are available with count */ static hasServiceCountPattern(content: string): boolean { return /services.*available|found.*services|services.*found|services.*list|available.*services/i.test(content); } /** * Check if response contains service descriptions */ static hasServiceDescriptionPattern(content: string): boolean { return /service.*description|service.*details|service.*information/i.test(content); } /** * Check if response contains a list of services */ static hasServiceListPattern(content: string): boolean { return /services.*:|services.*are|services.*include|here.*services/i.test(content); } /** * Create a validator that checks for marketplace service registration responses * Accepts various response types for service registration */ static createMarketplaceServiceRegistrationValidator(): (content: string) => boolean { return (content: string) => { // Reject processing messages like "please wait" or "hold on" // These messages indicate the AI is still working and haven't provided the actual result yet const hasProcessingWords = /please wait|hold on|checking|verifying|moment|gathering|processing|working on/i.test(content); const hasProcessingMessage = hasProcessingWords; if (hasProcessingMessage) { console.log('Marketplace service registration validation: Rejecting processing message'); return false; } // Check for successful service registration patterns const registrationSuccessPatterns = [ // Original patterns /service.*registered/i, /service.*created/i, /service.*successfully.*registered/i, /service.*successfully.*created/i, /registration.*successful/i, /registration.*completed/i, /service.*added/i, /service.*listed/i, /service.*available/i, /service.*ready/i, /service.*active/i, /service.*published/i, /service.*live/i, /service.*activated/i, /service.*confirmed/i, /service.*processed/i, /service.*executed/i, /service.*initiated/i, /service.*set up/i, /service.*configured/i, /service.*established/i, /service.*enabled/i, /service.*deployed/i, /service.*launched/i, /service.*started/i, /service.*begun/i, /service.*commenced/i, /service.*initiated/i, /service.*began/i, /service.*started/i, /service.*launched/i, /service.*deployed/i, /service.*enabled/i, /service.*established/i, /service.*configured/i, /service.*set up/i, /service.*processed/i, /service.*executed/i, /service.*confirmed/i, /service.*live/i, /service.*published/i, /service.*active/i, /service.*ready/i, /service.*available/i, /service.*listed/i, /service.*added/i, /service.*completed/i, /service.*successful/i, /service.*created/i, /service.*registered/i, // Patterns for the actual response format from Eliza messages /new service.*successfully registered/i, /service.*has been successfully registered/i, /service id.*[a-f0-9-]{36}/i, // UUID pattern for service ID /service id.*[a-f0-9-]{8}-[a-f0-9-]{4}-[a-f0-9-]{4}-[a-f0-9-]{4}-[a-f0-9-]{12}/i, // Full UUID pattern // Additional patterns based on actual Eliza response /successfully registered/i, /been successfully registered/i, /service.*details.*need/i, /service id.*[a-f0-9-]+/i, // More flexible UUID pattern /- \*\*Service ID:\*\*/i, // Markdown format from Eliza /- \*\*Description:\*\*/i, // Markdown format from Eliza // Additional patterns for various response types /service.*id/i, /service.*information/i, /service.*details/i, /service.*data/i, /service.*record/i, /service.*entry/i, /service.*listing/i, /service.*profile/i, /service.*account/i, /service.*registration/i, /marketplace.*service/i, /marketplace.*registration/i, /marketplace.*listing/i, /marketplace.*entry/i, /marketplace.*record/i, /marketplace.*profile/i, /marketplace.*account/i, /marketplace.*data/i, /marketplace.*information/i, /marketplace.*details/i, /register.*service/i, /register.*marketplace/i, /create.*service/i, /create.*marketplace/i, /add.*service/i, /add.*marketplace/i, /list.*service/i, /list.*marketplace/i, // General success indicators in service context /success.*service/i, /success.*marketplace/i, /completed.*service/i, /completed.*marketplace/i, /done.*service/i, /done.*marketplace/i, /finished.*service/i, /finished.*marketplace/i, /ready.*service/i, /ready.*marketplace/i, /available.*service/i, /available.*marketplace/i, /active.*service/i, /active.*marketplace/i, /live.*service/i, /live.*marketplace/i, /published.*service/i, /published.*marketplace/i, /listed.*service/i, /listed.*marketplace/i, /added.*service/i, /added.*marketplace/i, /created.*service/i, /created.*marketplace/i, /registered.*service/i, /registered.*marketplace/i ]; const hasRegistrationSuccess = registrationSuccessPatterns.some(pattern => pattern.test(content)); // Also check for general success indicators in the context of service registration const hasSuccessIndicators = this.hasSuccessIndicators(content); // Check for marketplace/service-related keywords to ensure it's a service registration response const hasServiceKeywords = /service|marketplace|register|registration|create|add|list/i.test(content); // Check for service ID presence (most definitive indicator) const hasServiceId = /service id.*[a-f0-9-]+/i.test(content) || /- \*\*Service ID:\*\*/i.test(content); // Check for markdown format from Eliza const hasMarkdownFormat = /- \*\*Service ID:\*\*/i.test(content) || /- \*\*Description:\*\*/i.test(content); // Check for any response that mentions service or marketplace const hasAnyServiceOrMarketplaceMention = /service|marketplace/i.test(content); // Check for any response that's not an error const hasNoErrorIndicators = !this.hasErrorIndicators(content); // Validation - accept if we have any of: // 1. Service ID (most definitive) // 2. Registration success patterns AND service-related keywords // 3. Markdown format from Eliza (indicates structured response) // 4. Any service/marketplace mention with no error indicators // 5. General success indicators with service keywords const isValid = hasServiceId || (hasRegistrationSuccess && hasServiceKeywords) || hasMarkdownFormat || (hasAnyServiceOrMarketplaceMention && hasNoErrorIndicators) || (hasSuccessIndicators && hasServiceKeywords); if (!isValid) { console.log('Marketplace service registration validation failed for content:', content.substring(0, 200) + '...'); console.log('Validation details:', { hasRegistrationSuccess, hasSuccessIndicators, hasServiceKeywords, hasProcessingMessage, hasServiceId, hasMarkdownFormat, hasAnyServiceOrMarketplaceMention, hasNoErrorIndicators }); } else { console.log('Marketplace service registration validation succeeded'); console.log('Success reason:', { hasServiceId: hasServiceId, hasRegistrationSuccess: hasRegistrationSuccess, hasServiceKeywords: hasServiceKeywords, hasMarkdownFormat: hasMarkdownFormat, hasAnyServiceOrMarketplaceMention: hasAnyServiceOrMarketplaceMention, hasNoErrorIndicators: hasNoErrorIndicators }); } return isValid; }; } /** * Create a simple validator for service content storage * Accepts simple success keywords and rejects "please wait" messages */ static createServiceContentStorageValidator(): (content: string) => boolean { return (content: string) => { // Reject processing messages like "please wait" or "hold on" const hasProcessingWords = /please wait|hold on|checking|verifying|moment|gathering|processing|working on/i.test(content); const hasProcessingMessage = hasProcessingWords; if (hasProcessingMessage) { console.log('Service content storage validation: Rejecting processing message'); return false; } // Reject error messages const hasErrorWords = /issue|problem|error|failed|sorry|inconvenience|try again|adjustments/i.test(content); const hasErrorMessage = hasErrorWords; if (hasErrorMessage) { console.log('Service content storage validation: Rejecting error message'); return false; } // Simple list of success keywords for content storage const successKeywords = [ 'registered', 'stored', 'saved', 'ready', 'added', 'updated', 'content', 'service' ]; const hasSuccessKeyword = successKeywords.some(keyword => content.toLowerCase().includes(keyword.toLowerCase()) ); const isValid = hasSuccessKeyword; if (!isValid) { console.log('Service content storage validation failed for content:', content.substring(0, 200) + '...'); } else { console.log('Service content storage validation succeeded'); } return isValid; }; } } /** * Test result formatter */ export class TestResultFormatter { /** * Format test result for logging */ static formatResult(testName: string, result: TestResult): string { const status = result.passed ? '✅ PASS' : '❌ FAIL'; const message = result.message || 'No message provided'; const error = result.error ? `\nError: ${result.error}` : ''; return `${status} ${testName}\n${message}${error}`; } /** * Format test summary */ static formatSummary(results: Array<{ name: string; result: TestResult }>): string { const passed = results.filter(r => r.result.passed).length; const total = results.length; const failed = total - passed; return ` Test Summary: ✅ Passed: ${passed} ❌ Failed: ${failed} 📊 Total: ${total} 📈 Success Rate: ${((passed / total) * 100).toFixed(1)}% `.trim(); } } /** * Wait utility for async operations */ export class WaitUtils { /** * Wait for a specified amount of time */ static async wait(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Wait between sequential tests to ensure proper integration flow * @param logger Optional logger to log the wait message * @param delayMs Delay in milliseconds (default: 100 for 100 milliseconds) */ static async waitBetweenTests(logger?: TestLogger, delayMs: number = 100): Promise<void> { if (logger) { logger.info(`Waiting ${delayMs / 1000} seconds before next test...`); } await WaitUtils.wait(delayMs); } /** * Wait for a condition to be true */ static async waitFor( condition: () => boolean | Promise<boolean>, timeout: number = 30000, interval: number = 1000 ): Promise<boolean> { const startTime = Date.now(); while (Date.now() - startTime < timeout) { if (await condition()) { return true; } await this.wait(interval); } return false; } } /** * Logger for test operations */ export class TestLogger { private prefix: string; constructor(prefix: string = 'TEST') { this.prefix = prefix; } info(message: string, data?: any): void { console.log(`[${this.prefix}] ℹ️ ${message}`); if (data) { console.log(JSON.stringify(data, null, 2)); } } success(message: string, data?: any): void { console.log(`[${this.prefix}] ✅ ${message}`); if (data) { console.log(JSON.stringify(data, null, 2)); } } error(message: string, error?: any): void { console.error(`[${this.prefix}] ❌ ${message}`); if (error) { console.error(error); } } warn(message: string, data?: any): void { console.warn(`[${this.prefix}] ⚠️ ${message}`); if (data) { console.warn(JSON.stringify(data, null, 2)); } } } // Export the new Eliza client for better functionality export { ElizaClient, createElizaClient } from './eliza-client';

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/evilpixi/pixi-midnight-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server