| |
| import { MODEL_ALIASES } from './aliases.js' |
| import { isModelAllowed } from './modelAllowlist.js' |
| import { getAPIProvider } from './providers.js' |
| import { sideQuery } from '../sideQuery.js' |
| import { |
| NotFoundError, |
| APIError, |
| APIConnectionError, |
| AuthenticationError, |
| } from '@anthropic-ai/sdk' |
| import { getModelStrings } from './modelStrings.js' |
|
|
| |
| const validModelCache = new Map<string, boolean>() |
|
|
| |
| |
| |
| export async function validateModel( |
| model: string, |
| ): Promise<{ valid: boolean; error?: string }> { |
| const normalizedModel = model.trim() |
|
|
| |
| if (!normalizedModel) { |
| return { valid: false, error: 'Model name cannot be empty' } |
| } |
|
|
| |
| if (!isModelAllowed(normalizedModel)) { |
| return { |
| valid: false, |
| error: `Model '${normalizedModel}' is not in the list of available models`, |
| } |
| } |
|
|
| |
| const lowerModel = normalizedModel.toLowerCase() |
| if ((MODEL_ALIASES as readonly string[]).includes(lowerModel)) { |
| return { valid: true } |
| } |
|
|
| |
| if (normalizedModel === process.env.ANTHROPIC_CUSTOM_MODEL_OPTION) { |
| return { valid: true } |
| } |
|
|
| |
| if (validModelCache.has(normalizedModel)) { |
| return { valid: true } |
| } |
|
|
|
|
| |
| try { |
| await sideQuery({ |
| model: normalizedModel, |
| max_tokens: 1, |
| maxRetries: 0, |
| querySource: 'model_validation', |
| messages: [ |
| { |
| role: 'user', |
| content: [ |
| { |
| type: 'text', |
| text: 'Hi', |
| cache_control: { type: 'ephemeral' }, |
| }, |
| ], |
| }, |
| ], |
| }) |
|
|
| |
| validModelCache.set(normalizedModel, true) |
| return { valid: true } |
| } catch (error) { |
| return handleValidationError(error, normalizedModel) |
| } |
| } |
|
|
| function handleValidationError( |
| error: unknown, |
| modelName: string, |
| ): { valid: boolean; error: string } { |
| |
| if (error instanceof NotFoundError) { |
| const fallback = get3PFallbackSuggestion(modelName) |
| const suggestion = fallback ? `. Try '${fallback}' instead` : '' |
| return { |
| valid: false, |
| error: `Model '${modelName}' not found${suggestion}`, |
| } |
| } |
|
|
| |
| if (error instanceof APIError) { |
| if (error instanceof AuthenticationError) { |
| return { |
| valid: false, |
| error: 'Authentication failed. Please check your API credentials.', |
| } |
| } |
|
|
| if (error instanceof APIConnectionError) { |
| return { |
| valid: false, |
| error: 'Network error. Please check your internet connection.', |
| } |
| } |
|
|
| |
| const errorBody = error.error as unknown |
| if ( |
| errorBody && |
| typeof errorBody === 'object' && |
| 'type' in errorBody && |
| errorBody.type === 'not_found_error' && |
| 'message' in errorBody && |
| typeof errorBody.message === 'string' && |
| errorBody.message.includes('model:') |
| ) { |
| return { valid: false, error: `Model '${modelName}' not found` } |
| } |
|
|
| |
| return { valid: false, error: `API error: ${error.message}` } |
| } |
|
|
| |
| const errorMessage = error instanceof Error ? error.message : String(error) |
| return { |
| valid: false, |
| error: `Unable to validate model: ${errorMessage}`, |
| } |
| } |
|
|
| |
| |
| |
| |
| function get3PFallbackSuggestion(model: string): string | undefined { |
| if (getAPIProvider() === 'firstParty') { |
| return undefined |
| } |
| const lowerModel = model.toLowerCase() |
| if (lowerModel.includes('opus-4-6') || lowerModel.includes('opus_4_6')) { |
| return getModelStrings().opus41 |
| } |
| if (lowerModel.includes('sonnet-4-6') || lowerModel.includes('sonnet_4_6')) { |
| return getModelStrings().sonnet45 |
| } |
| if (lowerModel.includes('sonnet-4-5') || lowerModel.includes('sonnet_4_5')) { |
| return getModelStrings().sonnet40 |
| } |
| return undefined |
| } |
|
|