Fix: Validate GGUF files before loading (#6238)

This commit adds a GGUF validation check for both the main model file and the `mmproj` file (if present) before they are loaded. This prevents the extension from crashing if an invalid GGUF file is provided.

The `GgufMetadata` interface and `loadMetadata` function were removed as the `readGgufMetadata` is now invoked directly. The code has also been refactored to be more readable, with clearer variable names and more descriptive comments.
This commit is contained in:
Akarshan Biswas 2025-08-20 10:31:19 +05:30 committed by GitHub
parent b0eec07a01
commit 0fc3dc6841
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -104,12 +104,6 @@ interface DeviceList {
free: number free: number
} }
interface GgufMetadata {
version: number
tensor_count: number
metadata: Record<string, string>
}
/** /**
* Override the default app.log function to use Jan's logging system. * Override the default app.log function to use Jan's logging system.
* @param args * @param args
@ -1062,13 +1056,34 @@ export default class llamacpp_extension extends AIEngine {
} }
} }
// TODO: check if files are valid GGUF files // Validate GGUF files
// NOTE: modelPath and mmprojPath can be either relative to Jan's data folder (if they are downloaded)
// or absolute paths (if they are provided as local files)
const janDataFolderPath = await getJanDataFolderPath() const janDataFolderPath = await getJanDataFolderPath()
let size_bytes = ( const fullModelPath = await joinPath([janDataFolderPath, modelPath])
await fs.fileStat(await joinPath([janDataFolderPath, modelPath]))
).size try {
// Validate main model file
const modelMetadata = await readGgufMetadata(fullModelPath)
logger.info(
`Model GGUF validation successful: version ${modelMetadata.version}, tensors: ${modelMetadata.tensor_count}`
)
// Validate mmproj file if present
if (mmprojPath) {
const fullMmprojPath = await joinPath([janDataFolderPath, mmprojPath])
const mmprojMetadata = await readGgufMetadata(fullMmprojPath)
logger.info(
`Mmproj GGUF validation successful: version ${mmprojMetadata.version}, tensors: ${mmprojMetadata.tensor_count}`
)
}
} catch (error) {
logger.error('GGUF validation failed:', error)
throw new Error(
`Invalid GGUF file(s): ${error.message || 'File format validation failed'}`
)
}
// Calculate file sizes
let size_bytes = (await fs.fileStat(fullModelPath)).size
if (mmprojPath) { if (mmprojPath) {
size_bytes += ( size_bytes += (
await fs.fileStat(await joinPath([janDataFolderPath, mmprojPath])) await fs.fileStat(await joinPath([janDataFolderPath, mmprojPath]))
@ -1204,7 +1219,7 @@ export default class llamacpp_extension extends AIEngine {
// disable llama-server webui // disable llama-server webui
args.push('--no-webui') args.push('--no-webui')
const api_key = await this.generateApiKey(modelId, String(port)) const api_key = await this.generateApiKey(modelId, String(port))
envs["LLAMA_API_KEY"] = api_key envs['LLAMA_API_KEY'] = api_key
// model option is required // model option is required
// NOTE: model_path and mmproj_path can be either relative to Jan's data folder or absolute path // NOTE: model_path and mmproj_path can be either relative to Jan's data folder or absolute path
@ -1293,12 +1308,15 @@ export default class llamacpp_extension extends AIEngine {
try { try {
// TODO: add LIBRARY_PATH // TODO: add LIBRARY_PATH
const sInfo = await invoke<SessionInfo>('plugin:llamacpp|load_llama_model', { const sInfo = await invoke<SessionInfo>(
backendPath, 'plugin:llamacpp|load_llama_model',
libraryPath, {
args, backendPath,
envs, libraryPath,
}) args,
envs,
}
)
return sInfo return sInfo
} catch (error) { } catch (error) {
logger.error('Error in load command:\n', error) logger.error('Error in load command:\n', error)
@ -1389,7 +1407,10 @@ export default class llamacpp_extension extends AIEngine {
headers, headers,
body, body,
connectTimeout: 600000, // 10 minutes connectTimeout: 600000, // 10 minutes
signal: AbortSignal.any([AbortSignal.timeout(600000), abortController?.signal]), signal: AbortSignal.any([
AbortSignal.timeout(600000),
abortController?.signal,
]),
}) })
if (!response.ok) { if (!response.ok) {
const errorData = await response.json().catch(() => null) const errorData = await response.json().catch(() => null)
@ -1670,18 +1691,4 @@ export default class llamacpp_extension extends AIEngine {
'tokenizer.chat_template' 'tokenizer.chat_template'
]?.includes('tools') ]?.includes('tools')
} }
private async loadMetadata(path: string): Promise<GgufMetadata> {
try {
const data = await invoke<GgufMetadata>(
'plugin:llamacpp|read_gguf_metadata',
{
path: path,
}
)
return data
} catch (err) {
throw err
}
}
} }