@lobehub/chat
Version:
Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.
267 lines (234 loc) • 8.63 kB
text/typescript
import { AgentRuntimeErrorType, ILobeAgentRuntimeErrorType } from '../types/error';
export interface ComfyUIError {
code?: number | string;
details?: any;
message: string;
missingFileName?: string;
missingFileType?: 'model' | 'component';
status?: number;
type?: string;
userGuidance?: string;
}
export interface ParsedError {
error: ComfyUIError;
errorType: ILobeAgentRuntimeErrorType;
}
/**
* Clean ComfyUI error message by removing formatting characters and extra spaces
* @param message - Original error message
* @returns Cleaned error message
*/
export function cleanComfyUIErrorMessage(message: string): string {
return message
.replaceAll(/^\*\s*/gm, '') // Remove leading asterisks and spaces (multiline)
.replaceAll('\\n', '\n') // Convert escaped newlines
.replaceAll(/\n+/g, ' ') // Replace multiple newlines with single space
.trim(); // Remove leading and trailing spaces
}
/**
* Extract structured information from error object
* Client-side version that preserves server-generated information
* @param error - Original error object
* @returns Structured ComfyUI error information
*/
function extractComfyUIErrorInfo(error: any): ComfyUIError {
// Handle string errors
if (typeof error === 'string') {
const cleanedMessage = cleanComfyUIErrorMessage(error);
return {
message: cleanedMessage,
};
}
// Handle Error objects - prioritize cause field (SDK pattern)
if (error instanceof Error) {
// Check if there's a cause field with actual error details (SDK pattern)
if ((error as any).cause) {
const cause = (error as any).cause;
// Recursively extract error info from cause
const causeInfo = extractComfyUIErrorInfo(cause);
return {
...causeInfo,
// Preserve the original error type if cause doesn't have one
type: causeInfo.type || error.name,
};
}
const cleanedMessage = cleanComfyUIErrorMessage(error.message);
return {
code: (error as any).code,
message: cleanedMessage,
// Preserve server-generated file info and guidance
missingFileName: (error as any).missingFileName,
missingFileType: (error as any).missingFileType,
status: (error as any).status || (error as any).statusCode,
type: error.name,
userGuidance: (error as any).userGuidance,
};
}
// Handle structured objects
if (error && typeof error === 'object') {
// Check for cause field first (SDK pattern)
if (error.cause) {
const causeInfo = extractComfyUIErrorInfo(error.cause);
return {
...causeInfo,
type: causeInfo.type || error.type || error.name || error.constructor?.name,
};
}
// Extract message from various possible sources
const possibleMessage = [
error.exception_message, // ComfyUI specific field (highest priority)
error.error?.exception_message, // Nested ComfyUI exception message
error.error?.error, // Deeply nested error.error.error path
error.message,
error.error?.message,
error.data?.message,
error.body?.message,
error.body?.error?.message,
error.response?.data?.message,
error.response?.data?.error?.message,
error.response?.text,
error.response?.body,
error.statusText,
].find(Boolean);
const message = possibleMessage || String(error);
// Extract status code from various possible locations
const possibleStatus = [
error.status,
error.statusCode,
error.details?.status, // ServicesError puts status in details
error.response?.status,
error.response?.statusCode,
error.error?.status,
error.error?.statusCode,
].find(Number.isInteger);
const code = error.code || error.error?.code || error.response?.data?.code;
// Extract details including ComfyUI specific fields
let details = error.response?.data || error.details || undefined;
// Include ComfyUI specific fields in details
if (error.node_id || error.node_type || error.nodeId || error.nodeType || error.nodeName) {
details = {
...details,
nodeName: error.nodeName,
node_id: error.node_id || error.nodeId,
node_type: error.node_type || error.nodeType,
};
}
const cleanedMessage = cleanComfyUIErrorMessage(message);
// Extract server-provided file info and guidance from various locations
const missingFileName =
error.missingFileName || error.body?.error?.missingFileName || error.error?.missingFileName;
const missingFileType =
error.missingFileType || error.body?.error?.missingFileType || error.error?.missingFileType;
const userGuidance =
error.userGuidance || error.body?.error?.userGuidance || error.error?.userGuidance;
return {
code,
details,
message: cleanedMessage,
missingFileName,
missingFileType,
status: possibleStatus,
type: error.type || error.name || error.constructor?.name,
userGuidance,
};
}
// Fallback handling
const cleanedMessage = cleanComfyUIErrorMessage(String(error));
return {
message: cleanedMessage,
};
}
/**
* Parse ComfyUI error message and return structured error information
* Client-side version that focuses on error type categorization
* File information and userGuidance are expected from server-side error handling
* @param error - Original error object
* @returns Parsed error object and error type
*/
export function parseComfyUIErrorMessage(error: any): ParsedError {
// Check if it's already an AgentRuntimeError from WebAPI
// AgentRuntimeError has structure: { error: object, errorType: string, provider: string }
if (
error &&
typeof error === 'object' &&
'errorType' in error &&
'error' in error &&
'provider' in error
) {
// Already parsed by server, return as-is
return {
error: error.error,
errorType: error.errorType,
};
}
// Check if it's an error from checkAuth middleware
// Format: { body: any, errorType: string }
if (error && typeof error === 'object' && 'errorType' in error && 'body' in error) {
// Extract error message from body
let message = 'Authentication failed';
if (error.body?.error?.message) {
message = error.body.error.message;
} else if (error.body?.error && typeof error.body.error === 'string') {
message = error.body.error;
} else if (error.body?.message) {
message = error.body.message;
}
return {
error: {
message,
status: 401,
},
errorType: AgentRuntimeErrorType.InvalidProviderAPIKey,
};
}
const errorInfo = extractComfyUIErrorInfo(error);
// Default error type
let errorType: ILobeAgentRuntimeErrorType = AgentRuntimeErrorType.ComfyUIBizError;
// Note: SyntaxError checking moved to server-side errorHandlerService
// Client-side will never receive raw SyntaxError as it's already processed by server
// 1. HTTP status code errors (priority check)
const status = errorInfo.status;
const message = errorInfo.message;
switch (status) {
case 400:
case 401: {
// These trigger ComfyUIAuth component
errorType = AgentRuntimeErrorType.InvalidProviderAPIKey;
break;
}
case 403: {
// Permission denied
errorType = AgentRuntimeErrorType.PermissionDenied;
break;
}
case 404: {
// 404 should trigger ComfyUIAuth for baseURL errors
errorType = AgentRuntimeErrorType.InvalidProviderAPIKey;
break;
}
default: {
if (status && status >= 500) {
// Server errors
errorType = AgentRuntimeErrorType.ComfyUIServiceUnavailable;
}
// 2. Check HTTP status code from error message (when status field doesn't exist)
else if (!status && message) {
if (message.includes('HTTP 401') || message.includes('401')) {
errorType = AgentRuntimeErrorType.InvalidProviderAPIKey;
} else if (message.includes('HTTP 403') || message.includes('403')) {
errorType = AgentRuntimeErrorType.PermissionDenied;
} else if (message.includes('HTTP 404') || message.includes('404')) {
errorType = AgentRuntimeErrorType.InvalidProviderAPIKey;
} else if (message.includes('HTTP 400') || message.includes('400')) {
errorType = AgentRuntimeErrorType.InvalidProviderAPIKey;
}
}
}
}
// Note: Error type determination is done server-side
// Client receives pre-determined errorType from server
return {
error: errorInfo,
errorType,
};
}