source dump of claude code
at main 159 lines 4.6 kB view raw
1// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered 2import { MODEL_ALIASES } from './aliases.js' 3import { isModelAllowed } from './modelAllowlist.js' 4import { getAPIProvider } from './providers.js' 5import { sideQuery } from '../sideQuery.js' 6import { 7 NotFoundError, 8 APIError, 9 APIConnectionError, 10 AuthenticationError, 11} from '@anthropic-ai/sdk' 12import { getModelStrings } from './modelStrings.js' 13 14// Cache valid models to avoid repeated API calls 15const validModelCache = new Map<string, boolean>() 16 17/** 18 * Validates a model by attempting an actual API call. 19 */ 20export async function validateModel( 21 model: string, 22): Promise<{ valid: boolean; error?: string }> { 23 const normalizedModel = model.trim() 24 25 // Empty model is invalid 26 if (!normalizedModel) { 27 return { valid: false, error: 'Model name cannot be empty' } 28 } 29 30 // Check against availableModels allowlist before any API call 31 if (!isModelAllowed(normalizedModel)) { 32 return { 33 valid: false, 34 error: `Model '${normalizedModel}' is not in the list of available models`, 35 } 36 } 37 38 // Check if it's a known alias (these are always valid) 39 const lowerModel = normalizedModel.toLowerCase() 40 if ((MODEL_ALIASES as readonly string[]).includes(lowerModel)) { 41 return { valid: true } 42 } 43 44 // Check if it matches ANTHROPIC_CUSTOM_MODEL_OPTION (pre-validated by the user) 45 if (normalizedModel === process.env.ANTHROPIC_CUSTOM_MODEL_OPTION) { 46 return { valid: true } 47 } 48 49 // Check cache first 50 if (validModelCache.has(normalizedModel)) { 51 return { valid: true } 52 } 53 54 55 // Try to make an actual API call with minimal parameters 56 try { 57 await sideQuery({ 58 model: normalizedModel, 59 max_tokens: 1, 60 maxRetries: 0, 61 querySource: 'model_validation', 62 messages: [ 63 { 64 role: 'user', 65 content: [ 66 { 67 type: 'text', 68 text: 'Hi', 69 cache_control: { type: 'ephemeral' }, 70 }, 71 ], 72 }, 73 ], 74 }) 75 76 // If we got here, the model is valid 77 validModelCache.set(normalizedModel, true) 78 return { valid: true } 79 } catch (error) { 80 return handleValidationError(error, normalizedModel) 81 } 82} 83 84function handleValidationError( 85 error: unknown, 86 modelName: string, 87): { valid: boolean; error: string } { 88 // NotFoundError (404) means the model doesn't exist 89 if (error instanceof NotFoundError) { 90 const fallback = get3PFallbackSuggestion(modelName) 91 const suggestion = fallback ? `. Try '${fallback}' instead` : '' 92 return { 93 valid: false, 94 error: `Model '${modelName}' not found${suggestion}`, 95 } 96 } 97 98 // For other API errors, provide context-specific messages 99 if (error instanceof APIError) { 100 if (error instanceof AuthenticationError) { 101 return { 102 valid: false, 103 error: 'Authentication failed. Please check your API credentials.', 104 } 105 } 106 107 if (error instanceof APIConnectionError) { 108 return { 109 valid: false, 110 error: 'Network error. Please check your internet connection.', 111 } 112 } 113 114 // Check error body for model-specific errors 115 const errorBody = error.error as unknown 116 if ( 117 errorBody && 118 typeof errorBody === 'object' && 119 'type' in errorBody && 120 errorBody.type === 'not_found_error' && 121 'message' in errorBody && 122 typeof errorBody.message === 'string' && 123 errorBody.message.includes('model:') 124 ) { 125 return { valid: false, error: `Model '${modelName}' not found` } 126 } 127 128 // Generic API error 129 return { valid: false, error: `API error: ${error.message}` } 130 } 131 132 // For unknown errors, be safe and reject 133 const errorMessage = error instanceof Error ? error.message : String(error) 134 return { 135 valid: false, 136 error: `Unable to validate model: ${errorMessage}`, 137 } 138} 139 140// @[MODEL LAUNCH]: Add a fallback suggestion chain for the new model → previous version 141/** 142 * Suggest a fallback model for 3P users when the selected model is unavailable. 143 */ 144function get3PFallbackSuggestion(model: string): string | undefined { 145 if (getAPIProvider() === 'firstParty') { 146 return undefined 147 } 148 const lowerModel = model.toLowerCase() 149 if (lowerModel.includes('opus-4-6') || lowerModel.includes('opus_4_6')) { 150 return getModelStrings().opus41 151 } 152 if (lowerModel.includes('sonnet-4-6') || lowerModel.includes('sonnet_4_6')) { 153 return getModelStrings().sonnet45 154 } 155 if (lowerModel.includes('sonnet-4-5') || lowerModel.includes('sonnet_4_5')) { 156 return getModelStrings().sonnet40 157 } 158 return undefined 159}