forked from sanbuphy/learn-coding-agent
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvalidateModel.ts
More file actions
159 lines (142 loc) · 4.52 KB
/
validateModel.ts
File metadata and controls
159 lines (142 loc) · 4.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
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
}