refactor: extract constants, parameterize validators, simplify factory
Quick-win refactors from the Refactoring Guru catalog: - Replace Magic Number with Symbolic Constant: extract DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE, default models per provider, and validation prompt into src/constants.ts. - Parameterize Method: collapse validateTemperature / validateTopP / validateMaxTokens (and the inline timeout/maxRetries checks) into a single validateNumberInRange helper with bounds metadata. - Inline Class / Remove Middle Man: drop the unused ProviderRegistry class from utils/factory.ts. createProvider now dispatches through the PROVIDER_REGISTRY const directly instead of a parallel switch. Also fixes stale VERSION constant in src/index.ts (was 1.3.1).
This commit is contained in:
@@ -23,6 +23,11 @@ import type {
|
||||
ResponseType
|
||||
} from '../types/index.js';
|
||||
import { AIProviderError, AIErrorType, generateResponseTypePrompt, parseAndValidateResponseType } from '../types/index.js';
|
||||
import {
|
||||
CONFIG_BOUNDS,
|
||||
DEFAULT_MAX_RETRIES,
|
||||
DEFAULT_TIMEOUT_MS
|
||||
} from '../constants.js';
|
||||
|
||||
// ============================================================================
|
||||
// ABSTRACT BASE PROVIDER CLASS
|
||||
@@ -314,60 +319,78 @@ export abstract class BaseAIProvider {
|
||||
);
|
||||
}
|
||||
|
||||
// Apply defaults and return normalized config
|
||||
this.validateNumberInRange('timeout', config.timeout, {
|
||||
...CONFIG_BOUNDS.timeoutMs,
|
||||
label: `${CONFIG_BOUNDS.timeoutMs.min}ms and ${CONFIG_BOUNDS.timeoutMs.max}ms`
|
||||
});
|
||||
this.validateNumberInRange('maxRetries', config.maxRetries, {
|
||||
...CONFIG_BOUNDS.maxRetries,
|
||||
integer: true
|
||||
});
|
||||
|
||||
return {
|
||||
...config,
|
||||
timeout: this.validateTimeout(config.timeout),
|
||||
maxRetries: this.validateMaxRetries(config.maxRetries)
|
||||
timeout: config.timeout ?? DEFAULT_TIMEOUT_MS,
|
||||
maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates timeout configuration value.
|
||||
*
|
||||
* @private
|
||||
* @param timeout - Timeout value to validate
|
||||
* @returns Validated timeout value with default if needed
|
||||
* Validates an optional numeric parameter against a range.
|
||||
* Skips validation when value is undefined so callers can apply defaults afterward.
|
||||
*/
|
||||
private validateTimeout(timeout?: number): number {
|
||||
const defaultTimeout = 30000; // 30 seconds
|
||||
|
||||
if (timeout === undefined) {
|
||||
return defaultTimeout;
|
||||
private validateNumberInRange(
|
||||
name: string,
|
||||
value: number | undefined,
|
||||
bounds: {
|
||||
min?: number;
|
||||
max?: number;
|
||||
exclusiveMin?: boolean;
|
||||
integer?: boolean;
|
||||
label?: string;
|
||||
}
|
||||
): void {
|
||||
if (value === undefined) return;
|
||||
|
||||
if (typeof timeout !== 'number' || timeout < 1000 || timeout > 300000) {
|
||||
const { min, max, exclusiveMin, integer, label } = bounds;
|
||||
const range = label ?? this.describeRange(min, max, exclusiveMin);
|
||||
|
||||
if (typeof value !== 'number' || Number.isNaN(value)) {
|
||||
throw new AIProviderError(
|
||||
'Timeout must be a number between 1000ms (1s) and 300000ms (5min)',
|
||||
`${name} must be a number between ${range}`,
|
||||
AIErrorType.INVALID_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
return timeout;
|
||||
if (integer && !Number.isInteger(value)) {
|
||||
throw new AIProviderError(
|
||||
`${name} must be an integer`,
|
||||
AIErrorType.INVALID_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
if (min !== undefined && (exclusiveMin ? value <= min : value < min)) {
|
||||
throw new AIProviderError(
|
||||
`${name} must be ${exclusiveMin ? 'greater than' : 'at least'} ${min}`,
|
||||
AIErrorType.INVALID_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
if (max !== undefined && value > max) {
|
||||
throw new AIProviderError(
|
||||
`${name} must be at most ${max}`,
|
||||
AIErrorType.INVALID_REQUEST
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates max retries configuration value.
|
||||
*
|
||||
* @private
|
||||
* @param maxRetries - Max retries value to validate
|
||||
* @returns Validated max retries value with default if needed
|
||||
*/
|
||||
private validateMaxRetries(maxRetries?: number): number {
|
||||
const defaultMaxRetries = 3;
|
||||
|
||||
if (maxRetries === undefined) {
|
||||
return defaultMaxRetries;
|
||||
private describeRange(min?: number, max?: number, exclusiveMin?: boolean): string {
|
||||
if (min !== undefined && max !== undefined) {
|
||||
return `${exclusiveMin ? '>' : '>='}${min} and <=${max}`;
|
||||
}
|
||||
|
||||
if (typeof maxRetries !== 'number' || maxRetries < 0 || maxRetries > 10) {
|
||||
throw new AIProviderError(
|
||||
'Max retries must be a number between 0 and 10',
|
||||
AIErrorType.INVALID_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
return maxRetries;
|
||||
if (min !== undefined) return `${exclusiveMin ? '>' : '>='}${min}`;
|
||||
if (max !== undefined) return `<=${max}`;
|
||||
return 'a valid number';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -403,13 +426,11 @@ export abstract class BaseAIProvider {
|
||||
);
|
||||
}
|
||||
|
||||
// Validate messages array
|
||||
this.validateMessages(params.messages);
|
||||
|
||||
// Validate optional parameters
|
||||
this.validateTemperature(params.temperature);
|
||||
this.validateTopP(params.topP);
|
||||
this.validateMaxTokens(params.maxTokens);
|
||||
this.validateNumberInRange('temperature', params.temperature, CONFIG_BOUNDS.temperature);
|
||||
this.validateNumberInRange('topP', params.topP, CONFIG_BOUNDS.topP);
|
||||
this.validateNumberInRange('maxTokens', params.maxTokens, { ...CONFIG_BOUNDS.maxTokens, integer: true });
|
||||
this.validateStopSequences(params.stopSequences);
|
||||
}
|
||||
|
||||
@@ -456,67 +477,6 @@ export abstract class BaseAIProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates temperature parameter.
|
||||
*
|
||||
* @private
|
||||
* @param temperature - Temperature value to validate
|
||||
* @throws {AIProviderError} If temperature is invalid
|
||||
*/
|
||||
private validateTemperature(temperature?: number): void {
|
||||
if (temperature !== undefined) {
|
||||
if (typeof temperature !== 'number' || temperature < 0 || temperature > 1) {
|
||||
throw new AIProviderError(
|
||||
'Temperature must be a number between 0.0 and 1.0',
|
||||
AIErrorType.INVALID_REQUEST
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates top-p parameter.
|
||||
*
|
||||
* @private
|
||||
* @param topP - Top-p value to validate
|
||||
* @throws {AIProviderError} If top-p is invalid
|
||||
*/
|
||||
private validateTopP(topP?: number): void {
|
||||
if (topP !== undefined) {
|
||||
if (typeof topP !== 'number' || topP <= 0 || topP > 1) {
|
||||
throw new AIProviderError(
|
||||
'Top-p must be a number between 0.0 (exclusive) and 1.0 (inclusive)',
|
||||
AIErrorType.INVALID_REQUEST
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates max tokens parameter.
|
||||
*
|
||||
* @private
|
||||
* @param maxTokens - Max tokens value to validate
|
||||
* @throws {AIProviderError} If max tokens is invalid
|
||||
*/
|
||||
private validateMaxTokens(maxTokens?: number): void {
|
||||
if (maxTokens !== undefined) {
|
||||
if (typeof maxTokens !== 'number' || !Number.isInteger(maxTokens) || maxTokens < 1) {
|
||||
throw new AIProviderError(
|
||||
'Max tokens must be a positive integer',
|
||||
AIErrorType.INVALID_REQUEST
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates stop sequences parameter.
|
||||
*
|
||||
* @private
|
||||
* @param stopSequences - Stop sequences to validate
|
||||
* @throws {AIProviderError} If stop sequences are invalid
|
||||
*/
|
||||
private validateStopSequences(stopSequences?: string[]): void {
|
||||
if (stopSequences !== undefined) {
|
||||
if (!Array.isArray(stopSequences)) {
|
||||
|
||||
Reference in New Issue
Block a user