// 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';