fix: Address all code review issues for production readiness

Critical Issues Fixed:
 Remove hardcoded retry timing - use dynamic retry_after values
 Optimize regex patterns - use string operations + efficient patterns
 Improve error detection - use partial string matching instead of exact
 Fix exception swallowing - fail fast for config errors, allow network errors

Improvements Added:
 Structured error codes (OPENAI_AUTH_FAILED, OPENAI_QUOTA_EXHAUSTED, etc.)
 Additional error types (timeout_error, configuration_error)
 Length limits on error messages (prevent processing large inputs)
 Enhanced user guidance for new error types
 Updated test assertions for error codes

Security Enhancements:
 More efficient sanitization (string ops + simplified regex)
 Stricter bounds on regex patterns
 Input length validation

The implementation now meets all production readiness criteria from
the code review with robust error handling, enhanced security, and
improved reliability for OpenAI error detection and user messaging.
This commit is contained in:
leex279
2025-09-12 20:35:47 +02:00
parent f8a3054906
commit f2ce5f959e
5 changed files with 380 additions and 30 deletions

View File

@@ -24,7 +24,9 @@ export async function callKnowledgeAPI<T>(
// The ETag client extracts the error message but loses the structured details
// We need to reconstruct the structured error based on the status code and message
if (error.statusCode === 401 && error.message === "Invalid OpenAI API key") {
// Use status code and message patterns to identify OpenAI errors
// More reliable than exact string matching
if (error.statusCode === 401 && error.message.includes("OpenAI API key")) {
// This is our OpenAI authentication error
errorData = {
status: 401,
@@ -32,10 +34,11 @@ export async function callKnowledgeAPI<T>(
detail: {
error: "Invalid OpenAI API key",
message: "Please verify your OpenAI API key in Settings before starting a crawl.",
error_type: "authentication_failed"
error_type: "authentication_failed",
error_code: "OPENAI_AUTH_FAILED"
}
};
} else if (error.statusCode === 429 && error.message === "OpenAI quota exhausted") {
} else if (error.statusCode === 429 && error.message.includes("quota")) {
// This is our OpenAI quota error
errorData = {
status: 429,
@@ -43,10 +46,11 @@ export async function callKnowledgeAPI<T>(
detail: {
error: "OpenAI quota exhausted",
message: "Your OpenAI API key has no remaining credits. Please add credits to your account.",
error_type: "quota_exhausted"
error_type: "quota_exhausted",
error_code: "OPENAI_QUOTA_EXHAUSTED"
}
};
} else if (error.statusCode === 429 && error.message === "OpenAI API rate limit exceeded") {
} else if (error.statusCode === 429 && error.message.includes("rate limit")) {
// This is our rate limit error
errorData = {
status: 429,
@@ -55,10 +59,10 @@ export async function callKnowledgeAPI<T>(
error: "OpenAI API rate limit exceeded",
message: "Too many requests to OpenAI API. Please wait a moment and try again.",
error_type: "rate_limit",
retry_after: 30
error_code: "OPENAI_RATE_LIMIT"
}
};
} else if (error.statusCode === 502 && error.message === "OpenAI API error") {
} else if (error.statusCode === 502 && error.message.includes("OpenAI")) {
// This is our generic API error
errorData = {
status: 502,
@@ -66,7 +70,8 @@ export async function callKnowledgeAPI<T>(
detail: {
error: "OpenAI API error",
message: "OpenAI API error. Please check your API key configuration.",
error_type: "api_error"
error_type: "api_error",
error_code: "OPENAI_API_ERROR"
}
};
} else {
@@ -148,7 +153,16 @@ export async function uploadWithEnhancedErrors(
} catch (error: any) {
// Check if it's a timeout error
if (error instanceof Error && error.name === 'AbortError') {
const timeoutError = parseKnowledgeBaseError(new Error('Request timed out'));
const timeoutError = parseKnowledgeBaseError({
status: 408,
error: 'Request timed out',
detail: {
error: 'Request timeout',
message: 'The request took too long to complete. Please try again or check your network connection.',
error_type: 'timeout_error',
error_code: 'REQUEST_TIMEOUT'
}
});
throw timeoutError;
}

View File

@@ -11,7 +11,8 @@
export interface OpenAIErrorDetails {
error: string;
message: string;
error_type: 'quota_exhausted' | 'rate_limit' | 'api_error' | 'authentication_failed';
error_type: 'quota_exhausted' | 'rate_limit' | 'api_error' | 'authentication_failed' | 'timeout_error' | 'configuration_error';
error_code?: string; // Structured error code for reliable detection
tokens_used?: number;
retry_after?: number;
api_key_prefix?: string;
@@ -135,6 +136,12 @@ export function getDisplayErrorMessage(error: EnhancedError): string {
case 'api_error':
return `OpenAI API error: ${error.errorDetails.message}. Please check your API key configuration.`;
case 'timeout_error':
return `Request timed out. Please try again or check your network connection.`;
case 'configuration_error':
return `OpenAI API configuration error. Please check your API key settings.`;
default:
return error.errorDetails.message || error.message;
}
@@ -173,6 +180,10 @@ export function getErrorSeverity(error: EnhancedError): 'error' | 'warning' | 'i
return 'warning'; // Temporary - retry may work
case 'api_error':
return 'error'; // Likely configuration issue
case 'timeout_error':
return 'warning'; // Temporary - retry may work
case 'configuration_error':
return 'error'; // Configuration issue
default:
return 'error';
}
@@ -196,10 +207,18 @@ export function getErrorAction(error: EnhancedError): string | null {
case 'authentication_failed':
return 'Verify your OpenAI API key in Settings';
case 'rate_limit':
const retryAfter = error.errorDetails.retry_after || 30;
return `Wait ${retryAfter} seconds and try again`;
const retryAfter = error.errorDetails.retry_after;
if (retryAfter && retryAfter > 0) {
return `Wait ${retryAfter} seconds and try again`;
} else {
return 'Wait a moment and try again';
}
case 'api_error':
return 'Verify your OpenAI API key in Settings';
case 'timeout_error':
return 'Check your network connection and try again';
case 'configuration_error':
return 'Check your OpenAI API key in Settings';
default:
return null;
}