init claude-code
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered
|
||||
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'
|
||||
|
||||
// Cache valid models to avoid repeated API calls
|
||||
const validModelCache = new Map<string, boolean>()
|
||||
|
||||
/**
|
||||
* Validates a model by attempting an actual API call.
|
||||
*/
|
||||
export async function validateModel(
|
||||
model: string,
|
||||
): Promise<{ valid: boolean; error?: string }> {
|
||||
const normalizedModel = model.trim()
|
||||
|
||||
// Empty model is invalid
|
||||
if (!normalizedModel) {
|
||||
return { valid: false, error: 'Model name cannot be empty' }
|
||||
}
|
||||
|
||||
// Check against availableModels allowlist before any API call
|
||||
if (!isModelAllowed(normalizedModel)) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Model '${normalizedModel}' is not in the list of available models`,
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's a known alias (these are always valid)
|
||||
const lowerModel = normalizedModel.toLowerCase()
|
||||
if ((MODEL_ALIASES as readonly string[]).includes(lowerModel)) {
|
||||
return { valid: true }
|
||||
}
|
||||
|
||||
// Check if it matches ANTHROPIC_CUSTOM_MODEL_OPTION (pre-validated by the user)
|
||||
if (normalizedModel === process.env.ANTHROPIC_CUSTOM_MODEL_OPTION) {
|
||||
return { valid: true }
|
||||
}
|
||||
|
||||
// Check cache first
|
||||
if (validModelCache.has(normalizedModel)) {
|
||||
return { valid: true }
|
||||
}
|
||||
|
||||
|
||||
// Try to make an actual API call with minimal parameters
|
||||
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' },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// If we got here, the model is valid
|
||||
validModelCache.set(normalizedModel, true)
|
||||
return { valid: true }
|
||||
} catch (error) {
|
||||
return handleValidationError(error, normalizedModel)
|
||||
}
|
||||
}
|
||||
|
||||
function handleValidationError(
|
||||
error: unknown,
|
||||
modelName: string,
|
||||
): { valid: boolean; error: string } {
|
||||
// NotFoundError (404) means the model doesn't exist
|
||||
if (error instanceof NotFoundError) {
|
||||
const fallback = get3PFallbackSuggestion(modelName)
|
||||
const suggestion = fallback ? `. Try '${fallback}' instead` : ''
|
||||
return {
|
||||
valid: false,
|
||||
error: `Model '${modelName}' not found${suggestion}`,
|
||||
}
|
||||
}
|
||||
|
||||
// For other API errors, provide context-specific messages
|
||||
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.',
|
||||
}
|
||||
}
|
||||
|
||||
// Check error body for model-specific errors
|
||||
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` }
|
||||
}
|
||||
|
||||
// Generic API error
|
||||
return { valid: false, error: `API error: ${error.message}` }
|
||||
}
|
||||
|
||||
// For unknown errors, be safe and reject
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
return {
|
||||
valid: false,
|
||||
error: `Unable to validate model: ${errorMessage}`,
|
||||
}
|
||||
}
|
||||
|
||||
// @[MODEL LAUNCH]: Add a fallback suggestion chain for the new model → previous version
|
||||
/**
|
||||
* Suggest a fallback model for 3P users when the selected model is unavailable.
|
||||
*/
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user