feat: Add GPT-5 model variants and improve Codex execution logic. Addressed code review comments

This commit is contained in:
gsxdsm
2026-02-18 11:15:38 -08:00
parent d30296d559
commit 5c441f2313
64 changed files with 3628 additions and 2223 deletions

View File

@@ -117,6 +117,44 @@ export function isQuotaExhaustedError(error: unknown): boolean {
return false;
}
/**
* Check if an error indicates a model-not-found or model access issue
*
* @param error - The error to check
* @returns True if the error indicates the model doesn't exist or user lacks access
*/
export function isModelNotFoundError(error: unknown): boolean {
const message = error instanceof Error ? error.message : String(error || '');
const lowerMessage = message.toLowerCase();
return (
lowerMessage.includes('does not exist or you do not have access') ||
lowerMessage.includes('model_not_found') ||
lowerMessage.includes('invalid_model') ||
(lowerMessage.includes('model') &&
(lowerMessage.includes('does not exist') || lowerMessage.includes('not found')))
);
}
/**
* Check if an error indicates a stream disconnection
*
* @param error - The error to check
* @returns True if the error indicates the stream was disconnected
*/
export function isStreamDisconnectedError(error: unknown): boolean {
const message = error instanceof Error ? error.message : String(error || '');
const lowerMessage = message.toLowerCase();
return (
lowerMessage.includes('stream disconnected') ||
lowerMessage.includes('stream ended') ||
lowerMessage.includes('connection reset') ||
lowerMessage.includes('socket hang up') ||
lowerMessage.includes('econnreset')
);
}
/**
* Extract retry-after duration from rate limit error
*
@@ -154,11 +192,17 @@ export function classifyError(error: unknown): ErrorInfo {
const isCancellation = isCancellationError(message);
const isRateLimit = isRateLimitError(error);
const isQuotaExhausted = isQuotaExhaustedError(error);
const isModelNotFound = isModelNotFoundError(error);
const isStreamDisconnected = isStreamDisconnectedError(error);
const retryAfter = isRateLimit ? (extractRetryAfter(error) ?? 60) : undefined;
let type: ErrorType;
if (isAuth) {
type = 'authentication';
} else if (isModelNotFound) {
type = 'model_not_found';
} else if (isStreamDisconnected) {
type = 'stream_disconnected';
} else if (isQuotaExhausted) {
// Quota exhaustion takes priority over rate limit since it's more specific
type = 'quota_exhausted';
@@ -182,6 +226,8 @@ export function classifyError(error: unknown): ErrorInfo {
isCancellation,
isRateLimit,
isQuotaExhausted,
isModelNotFound,
isStreamDisconnected,
retryAfter,
originalError: error,
};
@@ -204,6 +250,14 @@ export function getUserFriendlyErrorMessage(error: unknown): string {
return 'Authentication failed. Please check your API key.';
}
if (info.isModelNotFound) {
return `Model not available: ${info.message}\n\nSome models require specific subscription plans or authentication methods. Try authenticating with 'codex login' or switch to a different model.`;
}
if (info.isStreamDisconnected) {
return `Connection interrupted: ${info.message}\n\nThe stream was disconnected before the response could complete. This may be caused by network issues, model access restrictions, or server timeouts. Try again or switch to a different model.`;
}
if (info.isQuotaExhausted) {
return 'Usage limit reached. Auto Mode has been paused. Please wait for your quota to reset or upgrade your plan.';
}
@@ -241,3 +295,25 @@ export function getUserFriendlyErrorMessage(error: unknown): string {
export function getErrorMessage(error: unknown): string {
return error instanceof Error ? error.message : 'Unknown error';
}
/**
* Log an error with a context message to stderr.
*
* Convenience utility for consistent error logging throughout the codebase.
* Outputs a formatted error line to stderr with an ❌ prefix and the context.
*
* @param error - The error value to log
* @param context - Descriptive context message indicating where/why the error occurred
*
* @example
* ```typescript
* try {
* await someOperation();
* } catch (error) {
* logError(error, 'Failed to perform some operation');
* }
* ```
*/
export function logError(error: unknown, context: string): void {
console.error(`${context}:`, error);
}

View File

@@ -10,10 +10,13 @@ export {
isAuthenticationError,
isRateLimitError,
isQuotaExhaustedError,
isModelNotFoundError,
isStreamDisconnectedError,
extractRetryAfter,
classifyError,
getUserFriendlyErrorMessage,
getErrorMessage,
logError,
} from './error-handler.js';
// Conversation utilities

View File

@@ -5,6 +5,8 @@ import {
isAuthenticationError,
isRateLimitError,
isQuotaExhaustedError,
isModelNotFoundError,
isStreamDisconnectedError,
extractRetryAfter,
classifyError,
getUserFriendlyErrorMessage,
@@ -179,6 +181,76 @@ describe('error-handler.ts', () => {
});
});
describe('isModelNotFoundError', () => {
it('should return true for "does not exist or you do not have access" errors', () => {
expect(
isModelNotFoundError(
new Error('The model `gpt-5.3-codex` does not exist or you do not have access to it.')
)
).toBe(true);
});
it('should return true for model_not_found errors', () => {
expect(isModelNotFoundError(new Error('model_not_found: gpt-5.3-codex'))).toBe(true);
});
it('should return true for invalid_model errors', () => {
expect(isModelNotFoundError(new Error('invalid_model: unknown model'))).toBe(true);
});
it('should return true for "model does not exist" errors', () => {
expect(isModelNotFoundError(new Error('The model does not exist'))).toBe(true);
});
it('should return true for "model not found" errors', () => {
expect(isModelNotFoundError(new Error('model not found'))).toBe(true);
});
it('should return false for regular errors', () => {
expect(isModelNotFoundError(new Error('Something went wrong'))).toBe(false);
expect(isModelNotFoundError(new Error('Network error'))).toBe(false);
});
it('should return false for null/undefined', () => {
expect(isModelNotFoundError(null)).toBe(false);
expect(isModelNotFoundError(undefined)).toBe(false);
});
});
describe('isStreamDisconnectedError', () => {
it('should return true for "stream disconnected" errors', () => {
expect(isStreamDisconnectedError(new Error('stream disconnected before completion'))).toBe(
true
);
});
it('should return true for "stream ended" errors', () => {
expect(isStreamDisconnectedError(new Error('stream ended unexpectedly'))).toBe(true);
});
it('should return true for "connection reset" errors', () => {
expect(isStreamDisconnectedError(new Error('connection reset by peer'))).toBe(true);
});
it('should return true for "socket hang up" errors', () => {
expect(isStreamDisconnectedError(new Error('socket hang up'))).toBe(true);
});
it('should return true for ECONNRESET errors', () => {
expect(isStreamDisconnectedError(new Error('ECONNRESET'))).toBe(true);
});
it('should return false for regular errors', () => {
expect(isStreamDisconnectedError(new Error('Something went wrong'))).toBe(false);
expect(isStreamDisconnectedError(new Error('Network error'))).toBe(false);
});
it('should return false for null/undefined', () => {
expect(isStreamDisconnectedError(null)).toBe(false);
expect(isStreamDisconnectedError(undefined)).toBe(false);
});
});
describe('extractRetryAfter', () => {
it('should extract retry-after from error message', () => {
const error = new Error('Rate limit exceeded. retry-after: 60');
@@ -298,6 +370,28 @@ describe('error-handler.ts', () => {
expect(result.isAbort).toBe(false);
});
it('should classify model not found errors', () => {
const error = new Error(
'The model `gpt-5.3-codex` does not exist or you do not have access to it.'
);
const result = classifyError(error);
expect(result.type).toBe('model_not_found');
expect(result.isModelNotFound).toBe(true);
expect(result.isStreamDisconnected).toBe(false);
expect(result.isAuth).toBe(false);
});
it('should classify stream disconnected errors', () => {
const error = new Error('stream disconnected before completion');
const result = classifyError(error);
expect(result.type).toBe('stream_disconnected');
expect(result.isStreamDisconnected).toBe(true);
expect(result.isModelNotFound).toBe(false);
expect(result.isAuth).toBe(false);
});
it('should classify execution errors (regular Error)', () => {
const error = new Error('Something went wrong');
const result = classifyError(error);
@@ -397,6 +491,24 @@ describe('error-handler.ts', () => {
expect(message).toBe('Authentication failed. Please check your API key.');
});
it('should return friendly message for model not found errors', () => {
const error = new Error(
'The model `gpt-5.3-codex` does not exist or you do not have access to it.'
);
const message = getUserFriendlyErrorMessage(error);
expect(message).toContain('Model not available');
expect(message).toContain('codex login');
});
it('should return friendly message for stream disconnected errors', () => {
const error = new Error('stream disconnected before completion');
const message = getUserFriendlyErrorMessage(error);
expect(message).toContain('Connection interrupted');
expect(message).toContain('stream was disconnected');
});
it('should return friendly message for quota exhausted errors', () => {
const error = new Error('overloaded_error');
const message = getUserFriendlyErrorMessage(error);