Merge pull request 'refactor: extract constants, parameterize validators, simplify factory' (#7) from refactor/quick-wins-factory-validators-constants into main
Reviewed-on: #7
This commit was merged in pull request #7.
This commit is contained in:
35
src/constants.ts
Normal file
35
src/constants.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Shared defaults used across all providers.
|
||||
*/
|
||||
|
||||
export const DEFAULT_TIMEOUT_MS = 30_000;
|
||||
export const DEFAULT_MAX_RETRIES = 3;
|
||||
export const DEFAULT_MAX_TOKENS = 1000;
|
||||
export const DEFAULT_TEMPERATURE = 0.7;
|
||||
|
||||
/**
|
||||
* Minimal prompt used to validate API credentials on initialization.
|
||||
*/
|
||||
export const VALIDATION_PROMPT = 'Hi';
|
||||
|
||||
export const DEFAULT_MODELS = {
|
||||
claude: 'claude-3-5-sonnet-20241022',
|
||||
openai: 'gpt-4o',
|
||||
gemini: 'gemini-2.5-flash',
|
||||
openwebui: 'llama3.1:latest'
|
||||
} as const;
|
||||
|
||||
export const DEFAULT_OPENWEBUI_BASE_URL = 'http://localhost:3000';
|
||||
|
||||
export const DEFAULT_ANTHROPIC_VERSION = '2023-06-01';
|
||||
|
||||
/**
|
||||
* Bounds for configuration value validation.
|
||||
*/
|
||||
export const CONFIG_BOUNDS = {
|
||||
timeoutMs: { min: 1000, max: 300_000 },
|
||||
maxRetries: { min: 0, max: 10 },
|
||||
temperature: { min: 0, max: 1 },
|
||||
topP: { min: 0, max: 1, exclusiveMin: true },
|
||||
maxTokens: { min: 1 }
|
||||
} as const;
|
||||
@@ -58,4 +58,4 @@ export const SUPPORTED_PROVIDERS = ['claude', 'openai', 'gemini', 'openwebui'] a
|
||||
/**
|
||||
* Package version
|
||||
*/
|
||||
export const VERSION = '1.3.1';
|
||||
export const VERSION = '2.0.0';
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -34,6 +34,13 @@ import type {
|
||||
} from '../types/index.js';
|
||||
import { BaseAIProvider } from './base.js';
|
||||
import { AIProviderError, AIErrorType } from '../types/index.js';
|
||||
import {
|
||||
DEFAULT_ANTHROPIC_VERSION,
|
||||
DEFAULT_MAX_TOKENS,
|
||||
DEFAULT_MODELS,
|
||||
DEFAULT_TEMPERATURE,
|
||||
VALIDATION_PROMPT
|
||||
} from '../constants.js';
|
||||
|
||||
// ============================================================================
|
||||
// TYPES AND INTERFACES
|
||||
@@ -153,9 +160,8 @@ export class ClaudeProvider extends BaseAIProvider {
|
||||
constructor(config: ClaudeConfig) {
|
||||
super(config);
|
||||
|
||||
// Set Claude-specific defaults
|
||||
this.defaultModel = config.defaultModel || 'claude-3-5-sonnet-20241022';
|
||||
this.version = config.version || '2023-06-01';
|
||||
this.defaultModel = config.defaultModel || DEFAULT_MODELS.claude;
|
||||
this.version = config.version || DEFAULT_ANTHROPIC_VERSION;
|
||||
|
||||
// Validate model name format
|
||||
this.validateModelName(this.defaultModel);
|
||||
@@ -329,11 +335,10 @@ export class ClaudeProvider extends BaseAIProvider {
|
||||
}
|
||||
|
||||
try {
|
||||
// Make minimal request to test connection and permissions
|
||||
await this.client.messages.create({
|
||||
model: this.defaultModel,
|
||||
max_tokens: 1,
|
||||
messages: [{ role: 'user', content: 'Hi' }]
|
||||
messages: [{ role: 'user', content: VALIDATION_PROMPT }]
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
@@ -449,8 +454,8 @@ export class ClaudeProvider extends BaseAIProvider {
|
||||
) {
|
||||
return {
|
||||
model: params.model || this.defaultModel,
|
||||
max_tokens: params.maxTokens || 1000,
|
||||
temperature: params.temperature ?? 0.7,
|
||||
max_tokens: params.maxTokens || DEFAULT_MAX_TOKENS,
|
||||
temperature: params.temperature ?? DEFAULT_TEMPERATURE,
|
||||
top_p: params.topP,
|
||||
stop_sequences: params.stopSequences,
|
||||
system: system || undefined,
|
||||
|
||||
@@ -31,6 +31,12 @@ import type {
|
||||
} from '../types/index.js';
|
||||
import { BaseAIProvider } from './base.js';
|
||||
import { AIProviderError, AIErrorType } from '../types/index.js';
|
||||
import {
|
||||
DEFAULT_MAX_TOKENS,
|
||||
DEFAULT_MODELS,
|
||||
DEFAULT_TEMPERATURE,
|
||||
VALIDATION_PROMPT
|
||||
} from '../constants.js';
|
||||
|
||||
// ============================================================================
|
||||
// TYPES AND INTERFACES
|
||||
@@ -95,7 +101,7 @@ export class GeminiProvider extends BaseAIProvider {
|
||||
constructor(config: GeminiConfig) {
|
||||
super(config);
|
||||
|
||||
this.defaultModel = config.defaultModel || 'gemini-2.5-flash';
|
||||
this.defaultModel = config.defaultModel || DEFAULT_MODELS.gemini;
|
||||
this.safetySettings = config.safetySettings;
|
||||
this.defaultGenerationConfig = config.generationConfig;
|
||||
|
||||
@@ -207,7 +213,7 @@ export class GeminiProvider extends BaseAIProvider {
|
||||
try {
|
||||
await this.client.models.generateContent({
|
||||
model: this.defaultModel,
|
||||
contents: [{ role: 'user', parts: [{ text: 'Hi' }] }],
|
||||
contents: [{ role: 'user', parts: [{ text: VALIDATION_PROMPT }] }],
|
||||
config: { maxOutputTokens: 1 }
|
||||
});
|
||||
} catch (error: any) {
|
||||
@@ -291,8 +297,8 @@ export class GeminiProvider extends BaseAIProvider {
|
||||
const defaults = this.defaultGenerationConfig;
|
||||
|
||||
const config: GenerateContentConfig = {
|
||||
temperature: params.temperature ?? defaults?.temperature ?? 0.7,
|
||||
maxOutputTokens: params.maxTokens ?? defaults?.maxOutputTokens ?? 1000
|
||||
temperature: params.temperature ?? defaults?.temperature ?? DEFAULT_TEMPERATURE,
|
||||
maxOutputTokens: params.maxTokens ?? defaults?.maxOutputTokens ?? DEFAULT_MAX_TOKENS
|
||||
};
|
||||
|
||||
const topP = params.topP ?? defaults?.topP;
|
||||
|
||||
@@ -35,6 +35,12 @@ import type {
|
||||
} from '../types/index.js';
|
||||
import { BaseAIProvider } from './base.js';
|
||||
import { AIProviderError, AIErrorType } from '../types/index.js';
|
||||
import {
|
||||
DEFAULT_MAX_TOKENS,
|
||||
DEFAULT_MODELS,
|
||||
DEFAULT_TEMPERATURE,
|
||||
VALIDATION_PROMPT
|
||||
} from '../constants.js';
|
||||
|
||||
// ============================================================================
|
||||
// TYPES AND INTERFACES
|
||||
@@ -173,7 +179,7 @@ export class OpenAIProvider extends BaseAIProvider {
|
||||
super(config);
|
||||
|
||||
// Set OpenAI-specific defaults
|
||||
this.defaultModel = config.defaultModel || 'gpt-4o';
|
||||
this.defaultModel = config.defaultModel || DEFAULT_MODELS.openai;
|
||||
this.organization = config.organization;
|
||||
this.project = config.project;
|
||||
|
||||
@@ -352,10 +358,9 @@ export class OpenAIProvider extends BaseAIProvider {
|
||||
}
|
||||
|
||||
try {
|
||||
// Make minimal request to test connection and permissions
|
||||
await this.client.chat.completions.create({
|
||||
model: this.defaultModel,
|
||||
messages: [{ role: 'user', content: 'Hi' }],
|
||||
messages: [{ role: 'user', content: VALIDATION_PROMPT }],
|
||||
max_tokens: 1
|
||||
});
|
||||
|
||||
@@ -435,8 +440,8 @@ export class OpenAIProvider extends BaseAIProvider {
|
||||
return {
|
||||
model: params.model || this.defaultModel,
|
||||
messages: this.convertMessages(params.messages),
|
||||
max_tokens: params.maxTokens || 1000,
|
||||
temperature: params.temperature ?? 0.7,
|
||||
max_tokens: params.maxTokens || DEFAULT_MAX_TOKENS,
|
||||
temperature: params.temperature ?? DEFAULT_TEMPERATURE,
|
||||
top_p: params.topP,
|
||||
stop: params.stopSequences,
|
||||
stream,
|
||||
|
||||
@@ -34,6 +34,12 @@ import type {
|
||||
} from '../types/index.js';
|
||||
import { BaseAIProvider } from './base.js';
|
||||
import { AIProviderError, AIErrorType } from '../types/index.js';
|
||||
import {
|
||||
DEFAULT_MAX_TOKENS,
|
||||
DEFAULT_MODELS,
|
||||
DEFAULT_OPENWEBUI_BASE_URL,
|
||||
DEFAULT_TEMPERATURE
|
||||
} from '../constants.js';
|
||||
|
||||
// ============================================================================
|
||||
// TYPES AND INTERFACES
|
||||
@@ -281,8 +287,8 @@ export class OpenWebUIProvider extends BaseAIProvider {
|
||||
super(config);
|
||||
|
||||
// Set OpenWebUI-specific defaults and normalize configuration
|
||||
this.defaultModel = config.defaultModel || 'llama3.1:latest';
|
||||
this.baseUrl = this.normalizeBaseUrl(config.baseUrl || 'http://localhost:3000');
|
||||
this.defaultModel = config.defaultModel || DEFAULT_MODELS.openwebui;
|
||||
this.baseUrl = this.normalizeBaseUrl(config.baseUrl || DEFAULT_OPENWEBUI_BASE_URL);
|
||||
this.useOllamaProxy = config.useOllamaProxy ?? false;
|
||||
this.dangerouslyAllowInsecureConnections = config.dangerouslyAllowInsecureConnections ?? true;
|
||||
|
||||
@@ -689,8 +695,8 @@ export class OpenWebUIProvider extends BaseAIProvider {
|
||||
const requestBody = {
|
||||
model: params.model || this.defaultModel,
|
||||
messages: this.convertMessages(params.messages),
|
||||
max_tokens: params.maxTokens || 1000,
|
||||
temperature: params.temperature ?? 0.7,
|
||||
max_tokens: params.maxTokens || DEFAULT_MAX_TOKENS,
|
||||
temperature: params.temperature ?? DEFAULT_TEMPERATURE,
|
||||
top_p: params.topP,
|
||||
stop: params.stopSequences,
|
||||
stream: false
|
||||
@@ -724,9 +730,9 @@ export class OpenWebUIProvider extends BaseAIProvider {
|
||||
prompt: prompt,
|
||||
stream: false,
|
||||
options: {
|
||||
temperature: params.temperature ?? 0.7,
|
||||
temperature: params.temperature ?? DEFAULT_TEMPERATURE,
|
||||
top_p: params.topP,
|
||||
num_predict: params.maxTokens || 1000,
|
||||
num_predict: params.maxTokens || DEFAULT_MAX_TOKENS,
|
||||
stop: params.stopSequences
|
||||
}
|
||||
};
|
||||
@@ -754,8 +760,8 @@ export class OpenWebUIProvider extends BaseAIProvider {
|
||||
const requestBody = {
|
||||
model: params.model || this.defaultModel,
|
||||
messages: this.convertMessages(params.messages),
|
||||
max_tokens: params.maxTokens || 1000,
|
||||
temperature: params.temperature ?? 0.7,
|
||||
max_tokens: params.maxTokens || DEFAULT_MAX_TOKENS,
|
||||
temperature: params.temperature ?? DEFAULT_TEMPERATURE,
|
||||
top_p: params.topP,
|
||||
stop: params.stopSequences,
|
||||
stream: true
|
||||
@@ -850,9 +856,9 @@ export class OpenWebUIProvider extends BaseAIProvider {
|
||||
prompt: prompt,
|
||||
stream: true,
|
||||
options: {
|
||||
temperature: params.temperature ?? 0.7,
|
||||
temperature: params.temperature ?? DEFAULT_TEMPERATURE,
|
||||
top_p: params.topP,
|
||||
num_predict: params.maxTokens || 1000,
|
||||
num_predict: params.maxTokens || DEFAULT_MAX_TOKENS,
|
||||
stop: params.stopSequences
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
/**
|
||||
* Factory utilities for creating AI providers
|
||||
* Provides convenient methods for instantiating and configuring providers
|
||||
* Factory utilities for creating AI providers.
|
||||
*/
|
||||
|
||||
import type { AIProviderConfig } from '../types/index.js';
|
||||
import { ClaudeProvider, type ClaudeConfig } from '../providers/claude.js';
|
||||
import { OpenAIProvider, type OpenAIConfig } from '../providers/openai.js';
|
||||
import { GeminiProvider, type GeminiConfig } from '../providers/gemini.js';
|
||||
import { OpenWebUIProvider, type OpenWebUIConfig } from '../providers/openwebui.js';
|
||||
import { BaseAIProvider } from '../providers/base.js';
|
||||
|
||||
/**
|
||||
* Supported AI provider types
|
||||
*/
|
||||
export type ProviderType = 'claude' | 'openai' | 'gemini' | 'openwebui';
|
||||
export const PROVIDER_REGISTRY = {
|
||||
claude: ClaudeProvider,
|
||||
openai: OpenAIProvider,
|
||||
gemini: GeminiProvider,
|
||||
openwebui: OpenWebUIProvider
|
||||
} as const;
|
||||
|
||||
export type ProviderType = keyof typeof PROVIDER_REGISTRY;
|
||||
|
||||
/**
|
||||
* Configuration map for different provider types
|
||||
*/
|
||||
export interface ProviderConfigMap {
|
||||
claude: ClaudeConfig;
|
||||
openai: OpenAIConfig;
|
||||
@@ -25,144 +24,38 @@ export interface ProviderConfigMap {
|
||||
openwebui: OpenWebUIConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function to create AI providers
|
||||
* @param type - The type of provider to create
|
||||
* @param config - Configuration for the provider
|
||||
* @returns Configured AI provider instance
|
||||
*/
|
||||
export function createProvider<T extends ProviderType>(
|
||||
type: T,
|
||||
config: ProviderConfigMap[T]
|
||||
): BaseAIProvider {
|
||||
switch (type) {
|
||||
case 'claude':
|
||||
return new ClaudeProvider(config as ClaudeConfig);
|
||||
case 'openai':
|
||||
return new OpenAIProvider(config as OpenAIConfig);
|
||||
case 'gemini':
|
||||
return new GeminiProvider(config as GeminiConfig);
|
||||
case 'openwebui':
|
||||
return new OpenWebUIProvider(config as OpenWebUIConfig);
|
||||
default:
|
||||
throw new Error(`Unsupported provider type: ${type}`);
|
||||
const ProviderClass = PROVIDER_REGISTRY[type];
|
||||
if (!ProviderClass) {
|
||||
throw new Error(`Unsupported provider type: ${type}`);
|
||||
}
|
||||
return new ProviderClass(config as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Claude provider with simplified configuration
|
||||
* @param apiKey - Anthropic API key
|
||||
* @param options - Optional additional configuration
|
||||
* @returns Configured Claude provider instance
|
||||
*/
|
||||
export function createClaudeProvider(
|
||||
apiKey: string,
|
||||
options: Partial<Omit<ClaudeConfig, 'apiKey'>> = {}
|
||||
): ClaudeProvider {
|
||||
return new ClaudeProvider({
|
||||
apiKey,
|
||||
...options
|
||||
});
|
||||
return new ClaudeProvider({ apiKey, ...options });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an OpenAI provider with simplified configuration
|
||||
* @param apiKey - OpenAI API key
|
||||
* @param options - Optional additional configuration
|
||||
* @returns Configured OpenAI provider instance
|
||||
*/
|
||||
export function createOpenAIProvider(
|
||||
apiKey: string,
|
||||
options: Partial<Omit<OpenAIConfig, 'apiKey'>> = {}
|
||||
): OpenAIProvider {
|
||||
return new OpenAIProvider({
|
||||
apiKey,
|
||||
...options
|
||||
});
|
||||
return new OpenAIProvider({ apiKey, ...options });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Gemini provider with simplified configuration
|
||||
* @param apiKey - Google AI API key
|
||||
* @param options - Optional additional configuration
|
||||
* @returns Configured Gemini provider instance
|
||||
*/
|
||||
export function createGeminiProvider(
|
||||
apiKey: string,
|
||||
options: Partial<Omit<GeminiConfig, 'apiKey'>> = {}
|
||||
): GeminiProvider {
|
||||
return new GeminiProvider({
|
||||
apiKey,
|
||||
...options
|
||||
});
|
||||
return new GeminiProvider({ apiKey, ...options });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an OpenWebUI provider instance
|
||||
*/
|
||||
export function createOpenWebUIProvider(config: OpenWebUIConfig): OpenWebUIProvider {
|
||||
return new OpenWebUIProvider(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider registry for dynamic provider creation
|
||||
*/
|
||||
export class ProviderRegistry {
|
||||
private static providers = new Map<string, new (config: AIProviderConfig) => BaseAIProvider>();
|
||||
|
||||
/**
|
||||
* Register a new provider type
|
||||
* @param name - Name of the provider
|
||||
* @param providerClass - Provider class constructor
|
||||
*/
|
||||
static register(name: string, providerClass: new (config: AIProviderConfig) => BaseAIProvider): void {
|
||||
this.providers.set(name.toLowerCase(), providerClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a provider by name
|
||||
* @param name - Name of the provider
|
||||
* @param config - Configuration for the provider
|
||||
* @returns Provider instance
|
||||
*/
|
||||
static create(name: string, config: AIProviderConfig): BaseAIProvider {
|
||||
const ProviderClass = this.providers.get(name.toLowerCase());
|
||||
if (!ProviderClass) {
|
||||
throw new Error(`Provider '${name}' is not registered`);
|
||||
}
|
||||
return new ProviderClass(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of registered provider names
|
||||
* @returns Array of registered provider names
|
||||
*/
|
||||
static getRegisteredProviders(): string[] {
|
||||
return Array.from(this.providers.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a provider is registered
|
||||
* @param name - Name of the provider
|
||||
* @returns True if provider is registered
|
||||
*/
|
||||
static isRegistered(name: string): boolean {
|
||||
return this.providers.has(name.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-register built-in providers
|
||||
ProviderRegistry.register('claude', ClaudeProvider);
|
||||
ProviderRegistry.register('openai', OpenAIProvider);
|
||||
ProviderRegistry.register('gemini', GeminiProvider);
|
||||
ProviderRegistry.register('openwebui', OpenWebUIProvider);
|
||||
|
||||
/**
|
||||
* Registry of all available providers
|
||||
*/
|
||||
export const PROVIDER_REGISTRY = {
|
||||
claude: ClaudeProvider,
|
||||
openai: OpenAIProvider,
|
||||
gemini: GeminiProvider,
|
||||
openwebui: OpenWebUIProvider
|
||||
} as const;
|
||||
Reference in New Issue
Block a user