inference-server 1.0.0-beta.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +216 -0
- package/dist/api/openai/enums.d.ts +4 -0
- package/dist/api/openai/enums.js +17 -0
- package/dist/api/openai/enums.js.map +1 -0
- package/dist/api/openai/handlers/chat.d.ts +3 -0
- package/dist/api/openai/handlers/chat.js +358 -0
- package/dist/api/openai/handlers/chat.js.map +1 -0
- package/dist/api/openai/handlers/completions.d.ts +3 -0
- package/dist/api/openai/handlers/completions.js +169 -0
- package/dist/api/openai/handlers/completions.js.map +1 -0
- package/dist/api/openai/handlers/embeddings.d.ts +3 -0
- package/dist/api/openai/handlers/embeddings.js +74 -0
- package/dist/api/openai/handlers/embeddings.js.map +1 -0
- package/dist/api/openai/handlers/images.d.ts +0 -0
- package/dist/api/openai/handlers/images.js +4 -0
- package/dist/api/openai/handlers/images.js.map +1 -0
- package/dist/api/openai/handlers/models.d.ts +3 -0
- package/dist/api/openai/handlers/models.js +23 -0
- package/dist/api/openai/handlers/models.js.map +1 -0
- package/dist/api/openai/handlers/transcription.d.ts +0 -0
- package/dist/api/openai/handlers/transcription.js +4 -0
- package/dist/api/openai/handlers/transcription.js.map +1 -0
- package/dist/api/openai/index.d.ts +7 -0
- package/dist/api/openai/index.js +14 -0
- package/dist/api/openai/index.js.map +1 -0
- package/dist/api/parseJSONRequestBody.d.ts +2 -0
- package/dist/api/parseJSONRequestBody.js +24 -0
- package/dist/api/parseJSONRequestBody.js.map +1 -0
- package/dist/api/v1/index.d.ts +2 -0
- package/dist/api/v1/index.js +29 -0
- package/dist/api/v1/index.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +10 -0
- package/dist/cli.js.map +1 -0
- package/dist/engines/gpt4all/engine.d.ts +34 -0
- package/dist/engines/gpt4all/engine.js +357 -0
- package/dist/engines/gpt4all/engine.js.map +1 -0
- package/dist/engines/gpt4all/util.d.ts +3 -0
- package/dist/engines/gpt4all/util.js +29 -0
- package/dist/engines/gpt4all/util.js.map +1 -0
- package/dist/engines/index.d.ts +19 -0
- package/dist/engines/index.js +21 -0
- package/dist/engines/index.js.map +1 -0
- package/dist/engines/node-llama-cpp/engine.d.ts +49 -0
- package/dist/engines/node-llama-cpp/engine.js +666 -0
- package/dist/engines/node-llama-cpp/engine.js.map +1 -0
- package/dist/engines/node-llama-cpp/types.d.ts +13 -0
- package/dist/engines/node-llama-cpp/types.js +2 -0
- package/dist/engines/node-llama-cpp/types.js.map +1 -0
- package/dist/engines/node-llama-cpp/util.d.ts +15 -0
- package/dist/engines/node-llama-cpp/util.js +84 -0
- package/dist/engines/node-llama-cpp/util.js.map +1 -0
- package/dist/engines/node-llama-cpp/validateModelFile.d.ts +8 -0
- package/dist/engines/node-llama-cpp/validateModelFile.js +36 -0
- package/dist/engines/node-llama-cpp/validateModelFile.js.map +1 -0
- package/dist/engines/stable-diffusion-cpp/engine.d.ts +90 -0
- package/dist/engines/stable-diffusion-cpp/engine.js +294 -0
- package/dist/engines/stable-diffusion-cpp/engine.js.map +1 -0
- package/dist/engines/stable-diffusion-cpp/types.d.ts +3 -0
- package/dist/engines/stable-diffusion-cpp/types.js +2 -0
- package/dist/engines/stable-diffusion-cpp/types.js.map +1 -0
- package/dist/engines/stable-diffusion-cpp/util.d.ts +4 -0
- package/dist/engines/stable-diffusion-cpp/util.js +55 -0
- package/dist/engines/stable-diffusion-cpp/util.js.map +1 -0
- package/dist/engines/stable-diffusion-cpp/validateModelFiles.d.ts +19 -0
- package/dist/engines/stable-diffusion-cpp/validateModelFiles.js +91 -0
- package/dist/engines/stable-diffusion-cpp/validateModelFiles.js.map +1 -0
- package/dist/engines/transformers-js/engine.d.ts +37 -0
- package/dist/engines/transformers-js/engine.js +538 -0
- package/dist/engines/transformers-js/engine.js.map +1 -0
- package/dist/engines/transformers-js/types.d.ts +7 -0
- package/dist/engines/transformers-js/types.js +2 -0
- package/dist/engines/transformers-js/types.js.map +1 -0
- package/dist/engines/transformers-js/util.d.ts +7 -0
- package/dist/engines/transformers-js/util.js +36 -0
- package/dist/engines/transformers-js/util.js.map +1 -0
- package/dist/engines/transformers-js/validateModelFiles.d.ts +17 -0
- package/dist/engines/transformers-js/validateModelFiles.js +133 -0
- package/dist/engines/transformers-js/validateModelFiles.js.map +1 -0
- package/dist/experiments/ChatWithVision.d.ts +11 -0
- package/dist/experiments/ChatWithVision.js +91 -0
- package/dist/experiments/ChatWithVision.js.map +1 -0
- package/dist/experiments/StableDiffPromptGenerator.d.ts +0 -0
- package/dist/experiments/StableDiffPromptGenerator.js +4 -0
- package/dist/experiments/StableDiffPromptGenerator.js.map +1 -0
- package/dist/experiments/VoiceFunctionCall.d.ts +18 -0
- package/dist/experiments/VoiceFunctionCall.js +51 -0
- package/dist/experiments/VoiceFunctionCall.js.map +1 -0
- package/dist/http.d.ts +19 -0
- package/dist/http.js +54 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/instance.d.ts +88 -0
- package/dist/instance.js +594 -0
- package/dist/instance.js.map +1 -0
- package/dist/lib/acquireFileLock.d.ts +7 -0
- package/dist/lib/acquireFileLock.js +38 -0
- package/dist/lib/acquireFileLock.js.map +1 -0
- package/dist/lib/calculateContextIdentity.d.ts +7 -0
- package/dist/lib/calculateContextIdentity.js +39 -0
- package/dist/lib/calculateContextIdentity.js.map +1 -0
- package/dist/lib/calculateFileChecksum.d.ts +1 -0
- package/dist/lib/calculateFileChecksum.js +16 -0
- package/dist/lib/calculateFileChecksum.js.map +1 -0
- package/dist/lib/copyDirectory.d.ts +6 -0
- package/dist/lib/copyDirectory.js +27 -0
- package/dist/lib/copyDirectory.js.map +1 -0
- package/dist/lib/decodeAudio.d.ts +1 -0
- package/dist/lib/decodeAudio.js +26 -0
- package/dist/lib/decodeAudio.js.map +1 -0
- package/dist/lib/downloadModelFile.d.ts +10 -0
- package/dist/lib/downloadModelFile.js +58 -0
- package/dist/lib/downloadModelFile.js.map +1 -0
- package/dist/lib/flattenMessageTextContent.d.ts +2 -0
- package/dist/lib/flattenMessageTextContent.js +11 -0
- package/dist/lib/flattenMessageTextContent.js.map +1 -0
- package/dist/lib/getCacheDirPath.d.ts +12 -0
- package/dist/lib/getCacheDirPath.js +31 -0
- package/dist/lib/getCacheDirPath.js.map +1 -0
- package/dist/lib/loadImage.d.ts +12 -0
- package/dist/lib/loadImage.js +30 -0
- package/dist/lib/loadImage.js.map +1 -0
- package/dist/lib/logger.d.ts +12 -0
- package/dist/lib/logger.js +98 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/math.d.ts +7 -0
- package/dist/lib/math.js +30 -0
- package/dist/lib/math.js.map +1 -0
- package/dist/lib/resolveModelFileLocation.d.ts +15 -0
- package/dist/lib/resolveModelFileLocation.js +41 -0
- package/dist/lib/resolveModelFileLocation.js.map +1 -0
- package/dist/lib/util.d.ts +7 -0
- package/dist/lib/util.js +61 -0
- package/dist/lib/util.js.map +1 -0
- package/dist/lib/validateModelFile.d.ts +9 -0
- package/dist/lib/validateModelFile.js +62 -0
- package/dist/lib/validateModelFile.js.map +1 -0
- package/dist/lib/validateModelOptions.d.ts +3 -0
- package/dist/lib/validateModelOptions.js +23 -0
- package/dist/lib/validateModelOptions.js.map +1 -0
- package/dist/pool.d.ts +61 -0
- package/dist/pool.js +512 -0
- package/dist/pool.js.map +1 -0
- package/dist/server.d.ts +59 -0
- package/dist/server.js +221 -0
- package/dist/server.js.map +1 -0
- package/dist/standalone.d.ts +1 -0
- package/dist/standalone.js +306 -0
- package/dist/standalone.js.map +1 -0
- package/dist/store.d.ts +60 -0
- package/dist/store.js +203 -0
- package/dist/store.js.map +1 -0
- package/dist/types/completions.d.ts +57 -0
- package/dist/types/completions.js +2 -0
- package/dist/types/completions.js.map +1 -0
- package/dist/types/index.d.ts +326 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/docs/engines.md +28 -0
- package/docs/gpu.md +72 -0
- package/docs/http-api.md +147 -0
- package/examples/all-options.js +108 -0
- package/examples/chat-cli.js +56 -0
- package/examples/chat-server.js +65 -0
- package/examples/concurrency.js +70 -0
- package/examples/express.js +70 -0
- package/examples/pool.js +91 -0
- package/package.json +113 -0
- package/src/api/openai/enums.ts +20 -0
- package/src/api/openai/handlers/chat.ts +408 -0
- package/src/api/openai/handlers/completions.ts +196 -0
- package/src/api/openai/handlers/embeddings.ts +92 -0
- package/src/api/openai/handlers/images.ts +3 -0
- package/src/api/openai/handlers/models.ts +33 -0
- package/src/api/openai/handlers/transcription.ts +2 -0
- package/src/api/openai/index.ts +16 -0
- package/src/api/parseJSONRequestBody.ts +26 -0
- package/src/api/v1/DRAFT.md +16 -0
- package/src/api/v1/index.ts +37 -0
- package/src/cli.ts +9 -0
- package/src/engines/gpt4all/engine.ts +441 -0
- package/src/engines/gpt4all/util.ts +31 -0
- package/src/engines/index.ts +28 -0
- package/src/engines/node-llama-cpp/engine.ts +811 -0
- package/src/engines/node-llama-cpp/types.ts +17 -0
- package/src/engines/node-llama-cpp/util.ts +126 -0
- package/src/engines/node-llama-cpp/validateModelFile.ts +46 -0
- package/src/engines/stable-diffusion-cpp/engine.ts +369 -0
- package/src/engines/stable-diffusion-cpp/types.ts +54 -0
- package/src/engines/stable-diffusion-cpp/util.ts +58 -0
- package/src/engines/stable-diffusion-cpp/validateModelFiles.ts +119 -0
- package/src/engines/transformers-js/engine.ts +659 -0
- package/src/engines/transformers-js/types.ts +25 -0
- package/src/engines/transformers-js/util.ts +40 -0
- package/src/engines/transformers-js/validateModelFiles.ts +168 -0
- package/src/experiments/ChatWithVision.ts +103 -0
- package/src/experiments/StableDiffPromptGenerator.ts +2 -0
- package/src/experiments/VoiceFunctionCall.ts +71 -0
- package/src/http.ts +72 -0
- package/src/index.ts +7 -0
- package/src/instance.ts +723 -0
- package/src/lib/acquireFileLock.ts +38 -0
- package/src/lib/calculateContextIdentity.ts +53 -0
- package/src/lib/calculateFileChecksum.ts +18 -0
- package/src/lib/copyDirectory.ts +29 -0
- package/src/lib/decodeAudio.ts +39 -0
- package/src/lib/downloadModelFile.ts +70 -0
- package/src/lib/flattenMessageTextContent.ts +19 -0
- package/src/lib/getCacheDirPath.ts +34 -0
- package/src/lib/loadImage.ts +46 -0
- package/src/lib/logger.ts +112 -0
- package/src/lib/math.ts +31 -0
- package/src/lib/resolveModelFileLocation.ts +49 -0
- package/src/lib/util.ts +75 -0
- package/src/lib/validateModelFile.ts +71 -0
- package/src/lib/validateModelOptions.ts +31 -0
- package/src/pool.ts +651 -0
- package/src/server.ts +270 -0
- package/src/standalone.ts +320 -0
- package/src/store.ts +278 -0
- package/src/types/completions.ts +86 -0
- package/src/types/index.ts +488 -0
- package/tsconfig.json +29 -0
- package/tsconfig.release.json +11 -0
- package/vitest.config.ts +18 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import lockfile from 'proper-lockfile'
|
|
4
|
+
import { touchFileSync } from '#package/lib/util.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Acquire a lock on a file or directory, creating the resource if it does not exist.
|
|
8
|
+
* @param fileOrDirPath Path to the file or directory to lock.
|
|
9
|
+
* @param signal Abort signal to release the lock.
|
|
10
|
+
* @returns A function that releases the lock.
|
|
11
|
+
*/
|
|
12
|
+
export function acquireFileLock(fileOrDirPath: string, signal?: AbortSignal): Promise<() => void> {
|
|
13
|
+
const pathInfo = path.parse(fileOrDirPath)
|
|
14
|
+
const looksLikeFile = !!pathInfo.ext
|
|
15
|
+
const doesExist = fs.existsSync(fileOrDirPath)
|
|
16
|
+
if (looksLikeFile && !doesExist) {
|
|
17
|
+
touchFileSync(fileOrDirPath)
|
|
18
|
+
}
|
|
19
|
+
if (!looksLikeFile && !doesExist) {
|
|
20
|
+
fs.mkdirSync(fileOrDirPath, { recursive: true })
|
|
21
|
+
}
|
|
22
|
+
const isLocked = lockfile.checkSync(fileOrDirPath)
|
|
23
|
+
const lockExists = fs.existsSync(`${fileOrDirPath}.lock`)
|
|
24
|
+
if (!isLocked && lockExists) {
|
|
25
|
+
fs.unlinkSync(`${fileOrDirPath}.lock`)
|
|
26
|
+
}
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
lockfile.lock(fileOrDirPath, { retries: { forever: true } })
|
|
29
|
+
.then((release) => {
|
|
30
|
+
signal?.addEventListener('abort', release)
|
|
31
|
+
resolve(() => {
|
|
32
|
+
release()
|
|
33
|
+
signal?.removeEventListener('abort', release)
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
.catch(reject)
|
|
37
|
+
})
|
|
38
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import crypto from 'node:crypto'
|
|
2
|
+
import { ChatMessage } from '#package/types/index.js'
|
|
3
|
+
import { flattenMessageTextContent } from './flattenMessageTextContent.js'
|
|
4
|
+
|
|
5
|
+
export interface CalculateContextIdentityOptions {
|
|
6
|
+
messages?: ChatMessage[]
|
|
7
|
+
text?: string
|
|
8
|
+
dropLastMessage?: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function calculateContextIdentity({
|
|
12
|
+
messages,
|
|
13
|
+
text,
|
|
14
|
+
dropLastMessage = false,
|
|
15
|
+
}: CalculateContextIdentityOptions): string {
|
|
16
|
+
|
|
17
|
+
if (text) {
|
|
18
|
+
return text
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (messages?.length) {
|
|
22
|
+
const filteredMessages = messages.filter((message, i) => {
|
|
23
|
+
// remove all but the leading system message
|
|
24
|
+
if (message.role === 'system' && i !== 0) {
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
if (message.role === 'tool') {
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
const textContent = flattenMessageTextContent(message.content)
|
|
31
|
+
return !!textContent
|
|
32
|
+
})
|
|
33
|
+
if (dropLastMessage && filteredMessages.length > 1) {
|
|
34
|
+
if (filteredMessages[filteredMessages.length - 1].role !== 'user') {
|
|
35
|
+
console.warn('Dropping last message that is not a user message. This should not happen.')
|
|
36
|
+
}
|
|
37
|
+
filteredMessages.pop()
|
|
38
|
+
}
|
|
39
|
+
// we dont wanna json stringify because this would make message key order significant
|
|
40
|
+
const serializedMessages = filteredMessages
|
|
41
|
+
.map((message) => {
|
|
42
|
+
return message.role + ': ' + flattenMessageTextContent(message.content)
|
|
43
|
+
})
|
|
44
|
+
.join('\n')
|
|
45
|
+
const contextIdentity = crypto
|
|
46
|
+
.createHash('sha1')
|
|
47
|
+
.update(serializedMessages)
|
|
48
|
+
.digest('hex')
|
|
49
|
+
return contextIdentity
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return ''
|
|
53
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import crypto from 'node:crypto'
|
|
3
|
+
|
|
4
|
+
export function calculateFileChecksum(filePath: string, hashType = 'sha256'): Promise<string> {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
const fileStream = fs.createReadStream(filePath)
|
|
7
|
+
const result = crypto.createHash(hashType)
|
|
8
|
+
fileStream.on('error', reject)
|
|
9
|
+
fileStream.on('data', (chunk) => {
|
|
10
|
+
result.update(chunk)
|
|
11
|
+
})
|
|
12
|
+
fileStream.on('end', () => {
|
|
13
|
+
resolve(
|
|
14
|
+
result.digest('hex')
|
|
15
|
+
)
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Recursively copy a directory and its contents
|
|
6
|
+
* @param src - Source directory
|
|
7
|
+
* @param dest - Destination directory
|
|
8
|
+
*/
|
|
9
|
+
export async function copyDirectory(src: string, dest: string): Promise<void> {
|
|
10
|
+
// Ensure the destination directory exists, create it if it doesn't
|
|
11
|
+
await fs.mkdir(dest, { recursive: true })
|
|
12
|
+
|
|
13
|
+
// Read the contents of the source directory
|
|
14
|
+
const entries = await fs.readdir(src, { withFileTypes: true })
|
|
15
|
+
|
|
16
|
+
// Iterate through all the entries (files and directories)
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
const srcPath = path.join(src, entry.name)
|
|
19
|
+
const destPath = path.join(dest, entry.name)
|
|
20
|
+
|
|
21
|
+
if (entry.isDirectory()) {
|
|
22
|
+
// Recursively copy directories
|
|
23
|
+
await copyDirectory(srcPath, destPath)
|
|
24
|
+
} else {
|
|
25
|
+
// Copy files
|
|
26
|
+
await fs.copyFile(srcPath, destPath)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import decode from 'audio-decode'
|
|
2
|
+
import libSampleRate from '@alexanderolsen/libsamplerate-js'
|
|
3
|
+
|
|
4
|
+
interface ResampleOptions {
|
|
5
|
+
inputSampleRate?: number
|
|
6
|
+
outputSampleRate: number
|
|
7
|
+
nChannels?: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function resampleAudioBuffer(input: Float32Array, opts: ResampleOptions) {
|
|
11
|
+
const nChannels = opts.nChannels ?? 2
|
|
12
|
+
const inputSampleRate = opts.inputSampleRate ?? 44100
|
|
13
|
+
|
|
14
|
+
const resampler = await libSampleRate.create(nChannels, inputSampleRate, opts.outputSampleRate, {
|
|
15
|
+
// http://www.mega-nerd.com/SRC/api_full.html http://www.mega-nerd.com/SRC/api_simple.html
|
|
16
|
+
converterType: libSampleRate.ConverterType.SRC_SINC_BEST_QUALITY, // default SRC_SINC_FASTEST. see API for more
|
|
17
|
+
})
|
|
18
|
+
const resampledData = resampler.simple(input)
|
|
19
|
+
resampler.destroy()
|
|
20
|
+
return resampledData
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
export async function decodeAudio(fileBuffer: ArrayBuffer | Uint8Array, sampleRate: number = 44100) {
|
|
25
|
+
const decodedAudio = await decode(fileBuffer)
|
|
26
|
+
|
|
27
|
+
let audio = decodedAudio.getChannelData(0)
|
|
28
|
+
|
|
29
|
+
if (decodedAudio.sampleRate !== sampleRate) {
|
|
30
|
+
audio = await resampleAudioBuffer(audio, {
|
|
31
|
+
inputSampleRate: decodedAudio.sampleRate,
|
|
32
|
+
outputSampleRate: sampleRate,
|
|
33
|
+
nChannels: 1,
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return audio
|
|
38
|
+
|
|
39
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { downloadFile as createFileDownload } from 'ipull'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { FileDownloadProgress } from '#package/types/index.js'
|
|
5
|
+
import { resolveModelFileLocation } from '#package/lib/resolveModelFileLocation.js'
|
|
6
|
+
|
|
7
|
+
interface DownloadArgs {
|
|
8
|
+
url: string
|
|
9
|
+
filePath?: string
|
|
10
|
+
modelsCachePath: string
|
|
11
|
+
onProgress?: (progress: FileDownloadProgress) => void
|
|
12
|
+
signal?: AbortSignal
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function downloadModelFile({ url, filePath, modelsCachePath, onProgress, signal }: DownloadArgs) {
|
|
16
|
+
let downloadUrl = url
|
|
17
|
+
const parsedUrl = new URL(url)
|
|
18
|
+
if (parsedUrl.hostname === 'huggingface.co') {
|
|
19
|
+
// TODO support auth headers
|
|
20
|
+
// https://ido-pluto.github.io/ipull/#md:custom-headers
|
|
21
|
+
const pathnameParts = parsedUrl.pathname.split('/')
|
|
22
|
+
if (pathnameParts.length > 3 && pathnameParts[3] === 'blob') {
|
|
23
|
+
const newUrl = new URL(url)
|
|
24
|
+
pathnameParts[3] = 'resolve'
|
|
25
|
+
newUrl.pathname = pathnameParts.join('/')
|
|
26
|
+
if (newUrl.searchParams.get('download') !== 'true') {
|
|
27
|
+
newUrl.searchParams.set('download', 'true')
|
|
28
|
+
}
|
|
29
|
+
downloadUrl = newUrl.href
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const destinationFile = resolveModelFileLocation({
|
|
34
|
+
url: downloadUrl,
|
|
35
|
+
filePath,
|
|
36
|
+
modelsCachePath,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
fs.mkdirSync(path.dirname(destinationFile), { recursive: true })
|
|
40
|
+
// TODO split gguf file support, could regex filename and download the rest
|
|
41
|
+
// see https://ido-pluto.github.io/ipull/#md:download-file-from-parts
|
|
42
|
+
const controller = await createFileDownload({
|
|
43
|
+
url: downloadUrl,
|
|
44
|
+
savePath: destinationFile,
|
|
45
|
+
skipExisting: true,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
let partialBytes = 0
|
|
49
|
+
if (fs.existsSync(destinationFile)) {
|
|
50
|
+
partialBytes = fs.statSync(destinationFile).size
|
|
51
|
+
}
|
|
52
|
+
const progressInterval = setInterval(() => {
|
|
53
|
+
if (onProgress) {
|
|
54
|
+
onProgress({
|
|
55
|
+
file: destinationFile,
|
|
56
|
+
loadedBytes: controller.status.transferredBytes + partialBytes,
|
|
57
|
+
totalBytes: controller.status.totalBytes,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
}, 3000)
|
|
61
|
+
if (signal) {
|
|
62
|
+
signal.addEventListener('abort', () => {
|
|
63
|
+
controller.close()
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
await controller.download()
|
|
67
|
+
if (progressInterval) {
|
|
68
|
+
clearInterval(progressInterval)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChatMessage,
|
|
3
|
+
MessageContentPart,
|
|
4
|
+
MessageTextContentPart,
|
|
5
|
+
} from '#package/types/index.js'
|
|
6
|
+
|
|
7
|
+
function isTextContentPart(
|
|
8
|
+
part: MessageContentPart,
|
|
9
|
+
): part is MessageTextContentPart {
|
|
10
|
+
return part.type === 'text'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function flattenMessageTextContent(content: ChatMessage['content']): string {
|
|
14
|
+
if (typeof content === 'string') {
|
|
15
|
+
return content
|
|
16
|
+
}
|
|
17
|
+
const parts = content.filter(isTextContentPart)
|
|
18
|
+
return parts.map((part) => part.text).join('\n')
|
|
19
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import os from 'node:os'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Determines the appropriate cache directory for the current platform.
|
|
6
|
+
*
|
|
7
|
+
* Follows platform-specific conventions:
|
|
8
|
+
* - Windows: %LOCALAPPDATA%\Cache\inference-server
|
|
9
|
+
* - macOS: ~/Library/Caches/inference-server
|
|
10
|
+
* - Linux: $XDG_CACHE_HOME/inference-server or ~/.cache/inference-server
|
|
11
|
+
*
|
|
12
|
+
* @param {string} subDir - The name of the cache subdirectory
|
|
13
|
+
* @returns {string} The absolute path to the cache directory
|
|
14
|
+
*/
|
|
15
|
+
export function getCacheDirPath(subDir: string = ''): string {
|
|
16
|
+
const platform = process.platform
|
|
17
|
+
let basePath: string
|
|
18
|
+
|
|
19
|
+
switch (platform) {
|
|
20
|
+
case 'win32':
|
|
21
|
+
// Windows: Use %LOCALAPPDATA%\Cache
|
|
22
|
+
basePath = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local')
|
|
23
|
+
return path.join(basePath, 'Cache', 'inference-server', subDir)
|
|
24
|
+
|
|
25
|
+
case 'darwin':
|
|
26
|
+
// macOS: Use ~/Library/Caches
|
|
27
|
+
return path.join(os.homedir(), 'Library', 'Caches', 'inference-server', subDir)
|
|
28
|
+
|
|
29
|
+
default:
|
|
30
|
+
// Linux/Unix: Use $XDG_CACHE_HOME or ~/.cache
|
|
31
|
+
basePath = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache')
|
|
32
|
+
return path.join(basePath, 'inference-server', subDir)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Image } from '#package/types/index.js'
|
|
2
|
+
import sharp, { ResizeOptions } from 'sharp'
|
|
3
|
+
|
|
4
|
+
interface ImageTransformationOptions {
|
|
5
|
+
resize?: {
|
|
6
|
+
width: number
|
|
7
|
+
height: number
|
|
8
|
+
fit?: ResizeOptions['fit']
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function loadImageFromUrl(
|
|
13
|
+
url: string,
|
|
14
|
+
opts: ImageTransformationOptions = {},
|
|
15
|
+
): Promise<Image> {
|
|
16
|
+
const imageBuffer = await fetch(url).then((res) => res.arrayBuffer())
|
|
17
|
+
const buffer = await Buffer.from(imageBuffer)
|
|
18
|
+
const sharpHandle = sharp(buffer).rotate()
|
|
19
|
+
if (opts.resize) {
|
|
20
|
+
sharpHandle.resize(opts.resize)
|
|
21
|
+
}
|
|
22
|
+
const { info } = await sharpHandle.toBuffer({ resolveWithObject: true })
|
|
23
|
+
return {
|
|
24
|
+
handle: sharpHandle,
|
|
25
|
+
height: opts.resize?.height ?? info.height,
|
|
26
|
+
width: opts.resize?.width ?? info.width,
|
|
27
|
+
channels: info.channels,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function loadImageFromFile(
|
|
32
|
+
filePath: string,
|
|
33
|
+
opts: ImageTransformationOptions = {},
|
|
34
|
+
): Promise<Image> {
|
|
35
|
+
let sharpHandle = sharp(filePath).rotate()
|
|
36
|
+
if (opts.resize) {
|
|
37
|
+
sharpHandle.resize(opts.resize)
|
|
38
|
+
}
|
|
39
|
+
const { info } = await sharpHandle.toBuffer({ resolveWithObject: true })
|
|
40
|
+
return {
|
|
41
|
+
handle: sharpHandle,
|
|
42
|
+
height: info.height,
|
|
43
|
+
width: info.width,
|
|
44
|
+
channels: info.channels,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
|
|
3
|
+
export const LogLevels = {
|
|
4
|
+
error: 'error',
|
|
5
|
+
warn: 'warn',
|
|
6
|
+
info: 'info',
|
|
7
|
+
debug: 'debug',
|
|
8
|
+
verbose: 'verbose',
|
|
9
|
+
} as const
|
|
10
|
+
|
|
11
|
+
export type LogLevel = keyof typeof LogLevels
|
|
12
|
+
export type Logger = (level: LogLevel, message: string, meta?: any) => void
|
|
13
|
+
|
|
14
|
+
export function withLogMeta(logger: Logger, meta: object) {
|
|
15
|
+
return (level: LogLevel, message: string, extraMeta: object = {}) => {
|
|
16
|
+
logger(level, message, { ...meta, ...extraMeta })
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createSublogger(
|
|
21
|
+
minLevelOrLogger: LogLevel | Logger = LogLevels.warn,
|
|
22
|
+
) {
|
|
23
|
+
if (minLevelOrLogger) {
|
|
24
|
+
return typeof minLevelOrLogger === 'string'
|
|
25
|
+
? createLogger(minLevelOrLogger)
|
|
26
|
+
: minLevelOrLogger
|
|
27
|
+
} else {
|
|
28
|
+
return createLogger(LogLevels.warn)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createLogger(minLevel: LogLevel) {
|
|
33
|
+
const levels = Object.keys(LogLevels).reverse()
|
|
34
|
+
const minLevelIndex = levels.indexOf(minLevel)
|
|
35
|
+
|
|
36
|
+
return function log(level: LogLevel, message: string, meta?: any) {
|
|
37
|
+
const levelIndex = levels.indexOf(level)
|
|
38
|
+
if (levelIndex >= minLevelIndex) {
|
|
39
|
+
const formattedMessage = formatMessage(level, message, meta)
|
|
40
|
+
switch (level) {
|
|
41
|
+
case LogLevels.error:
|
|
42
|
+
console.error(formattedMessage, meta?.error)
|
|
43
|
+
break
|
|
44
|
+
case LogLevels.warn:
|
|
45
|
+
console.warn(formattedMessage)
|
|
46
|
+
break
|
|
47
|
+
case LogLevels.info:
|
|
48
|
+
console.info(formattedMessage)
|
|
49
|
+
break
|
|
50
|
+
case LogLevels.debug:
|
|
51
|
+
case LogLevels.verbose:
|
|
52
|
+
console.debug(formattedMessage)
|
|
53
|
+
break
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function formatMessage(level: LogLevel, message: string, meta: any = {}) {
|
|
60
|
+
const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 22)
|
|
61
|
+
let messageStr = `[${timestamp}]`
|
|
62
|
+
|
|
63
|
+
switch (level) {
|
|
64
|
+
case LogLevels.error:
|
|
65
|
+
messageStr += chalk.red(`[erro]`)
|
|
66
|
+
break
|
|
67
|
+
case LogLevels.warn:
|
|
68
|
+
messageStr += chalk.yellow(`[warn]`)
|
|
69
|
+
break
|
|
70
|
+
case LogLevels.info:
|
|
71
|
+
messageStr += chalk.white(`[info]`)
|
|
72
|
+
break
|
|
73
|
+
case LogLevels.debug:
|
|
74
|
+
messageStr += chalk.gray(`[debg]`)
|
|
75
|
+
break
|
|
76
|
+
case LogLevels.verbose:
|
|
77
|
+
messageStr += chalk.gray(`[verb]`)
|
|
78
|
+
break
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (meta?.sequence) {
|
|
82
|
+
messageStr += ' ' + chalk.cyan(meta.sequence)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (meta?.instance) {
|
|
86
|
+
messageStr += ' ' + chalk.magenta(meta.instance)
|
|
87
|
+
} else if (meta?.model) {
|
|
88
|
+
messageStr += ' ' + chalk.magenta(meta.model)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (meta?.task) {
|
|
92
|
+
messageStr += ' ' + chalk.green(meta.task)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
messageStr += ' ' + message
|
|
96
|
+
|
|
97
|
+
if (meta) {
|
|
98
|
+
const { instance, sequence, model, task, elapsed, error, ...otherData } =
|
|
99
|
+
meta
|
|
100
|
+
if (elapsed) {
|
|
101
|
+
if (elapsed < 1000) {
|
|
102
|
+
messageStr += ' ' + chalk.magenta(`+${elapsed.toFixed(2)}ms`)
|
|
103
|
+
} else {
|
|
104
|
+
messageStr += ' ' + chalk.magenta(`+${(elapsed / 1000).toFixed(2)}s`)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (Object.keys(otherData).length > 0) {
|
|
108
|
+
messageStr += ' ' + JSON.stringify(otherData, null, 2)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return messageStr
|
|
112
|
+
}
|
package/src/lib/math.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the dot product of two vectors.
|
|
3
|
+
* @param vecA - The first vector.
|
|
4
|
+
* @param vecB - The second vector.
|
|
5
|
+
* @returns The dot product of vecA and vecB.
|
|
6
|
+
*/
|
|
7
|
+
function dotProduct(vecA: number[], vecB: number[]): number {
|
|
8
|
+
return vecA.reduce((sum, value, index) => sum + value * vecB[index], 0)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Calculates the magnitude of a vector.
|
|
13
|
+
* @param vec - The vector.
|
|
14
|
+
* @returns The magnitude of the vector.
|
|
15
|
+
*/
|
|
16
|
+
function magnitude(vec: number[]): number {
|
|
17
|
+
return Math.sqrt(vec.reduce((sum, value) => sum + value * value, 0))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Calculates the cosine similarity between two vectors.
|
|
22
|
+
* @param vecA - The first vector.
|
|
23
|
+
* @param vecB - The second vector.
|
|
24
|
+
* @returns The cosine similarity between vecA and vecB.
|
|
25
|
+
*/
|
|
26
|
+
export function cosineSimilarity(vecA: number[], vecB: number[]): number {
|
|
27
|
+
const dotProd = dotProduct(vecA, vecB)
|
|
28
|
+
const magnitudeA = magnitude(vecA)
|
|
29
|
+
const magnitudeB = magnitude(vecB)
|
|
30
|
+
return dotProd / (magnitudeA * magnitudeB)
|
|
31
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
interface ResolveModelFileLocationArgs {
|
|
4
|
+
url?: string
|
|
5
|
+
filePath?: string
|
|
6
|
+
modelsCachePath: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a model file/url to an absolute path to either a file or directory.
|
|
11
|
+
* @param url - Optional URL to the model file. Location will be derived from it.
|
|
12
|
+
* @param filePath - Optional relative (to modelsCachePath) or absolute file path that short-circuits resolution.
|
|
13
|
+
* @param modelsCachePath - The path to the models cache directory.
|
|
14
|
+
* @returns The abs file path on the local filesystem.
|
|
15
|
+
* @throws If the model location could not be resolved.
|
|
16
|
+
*/
|
|
17
|
+
export function resolveModelFileLocation({ url, filePath, modelsCachePath }: ResolveModelFileLocationArgs) {
|
|
18
|
+
|
|
19
|
+
if (filePath) {
|
|
20
|
+
// immediately return if an absolute path is provided
|
|
21
|
+
if (path.isAbsolute(filePath)) {
|
|
22
|
+
return filePath
|
|
23
|
+
} else {
|
|
24
|
+
return path.join(modelsCachePath, filePath)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (url) {
|
|
29
|
+
const parsedUrl = new URL(url)
|
|
30
|
+
let destinationPath = filePath
|
|
31
|
+
// support branches for huggingface URLs
|
|
32
|
+
if (parsedUrl.hostname === 'huggingface.co' && !destinationPath) {
|
|
33
|
+
const pathnameSegments = parsedUrl.pathname.split('/')
|
|
34
|
+
const repoOrg = pathnameSegments[1]
|
|
35
|
+
const repoName = pathnameSegments[2]
|
|
36
|
+
const branch = pathnameSegments[4] || 'main'
|
|
37
|
+
const trailingPath = pathnameSegments.slice(5).join('/')
|
|
38
|
+
destinationPath = path.join(modelsCachePath, parsedUrl.hostname, repoOrg, `${repoName}-${branch}`, trailingPath)
|
|
39
|
+
}
|
|
40
|
+
// otherwise, use the hostname and last path segment
|
|
41
|
+
if (!destinationPath) {
|
|
42
|
+
const fileName = parsedUrl.pathname.split('/').pop()
|
|
43
|
+
destinationPath = path.join(modelsCachePath, parsedUrl.hostname, fileName || '')
|
|
44
|
+
}
|
|
45
|
+
return destinationPath
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
throw new Error('Failed to resolve model location')
|
|
49
|
+
}
|
package/src/lib/util.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { inspect } from 'node:util'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
export function elapsedMillis(since: bigint): number {
|
|
6
|
+
const now = process.hrtime.bigint()
|
|
7
|
+
return Number(now - BigInt(since)) / 1e6
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function omitEmptyValues<T extends Record<string, any>>(dict: T): T {
|
|
11
|
+
return Object.fromEntries(
|
|
12
|
+
Object.entries(dict).filter(([_, v]) => {
|
|
13
|
+
return v !== null && v !== undefined
|
|
14
|
+
}),
|
|
15
|
+
) as T
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function touchFileSync(filePath: string) {
|
|
19
|
+
fs.closeSync(fs.openSync(filePath, 'w'))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isSubpath(parent: string, child: string): boolean {
|
|
23
|
+
// Normalize paths to resolve .. and . segments
|
|
24
|
+
const normalizedParent = path.normalize(parent)
|
|
25
|
+
const normalizedChild = path.normalize(child)
|
|
26
|
+
|
|
27
|
+
// Get relative path from parent to child
|
|
28
|
+
const relativePath = path.relative(normalizedParent, normalizedChild)
|
|
29
|
+
|
|
30
|
+
// Check if relative path:
|
|
31
|
+
// 1. Doesn't start with .. (which would mean going up directories)
|
|
32
|
+
// 2. Isn't an absolute path (which would start with / or C:\ etc)
|
|
33
|
+
return !relativePath.startsWith('..') && !path.isAbsolute(relativePath)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function mergeAbortSignals(signals: Array<AbortSignal | undefined>): AbortSignal {
|
|
37
|
+
const controller = new AbortController()
|
|
38
|
+
const onAbort = () => {
|
|
39
|
+
controller.abort()
|
|
40
|
+
}
|
|
41
|
+
for (const signal of signals) {
|
|
42
|
+
if (signal) {
|
|
43
|
+
signal.addEventListener('abort', onAbort)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return controller.signal
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function getRandomNumber(min: number, max: number) {
|
|
50
|
+
min = Math.ceil(min)
|
|
51
|
+
max = Math.floor(max)
|
|
52
|
+
return Math.floor(Math.random() * (max - min)) + min
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function printActiveHandles() {
|
|
56
|
+
//@ts-ignore
|
|
57
|
+
const handles = process._getActiveHandles()
|
|
58
|
+
//@ts-ignore
|
|
59
|
+
const requests = process._getActiveRequests()
|
|
60
|
+
|
|
61
|
+
console.log('Active Handles:', inspect(handles, { depth: 1 }))
|
|
62
|
+
console.log('Active Requests:', inspect(requests, { depth: 1 }))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function formatBytesPerSecond(speed: number) {
|
|
66
|
+
const units = ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s']
|
|
67
|
+
let unitIndex = 0
|
|
68
|
+
|
|
69
|
+
while (speed >= 1024 && unitIndex < units.length - 1) {
|
|
70
|
+
speed /= 1024
|
|
71
|
+
unitIndex++
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return `${speed.toFixed(2)} ${units[unitIndex]}`
|
|
75
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import { calculateFileChecksum } from '#package/lib/calculateFileChecksum.js'
|
|
5
|
+
import { resolveModelFileLocation } from '#package/lib/resolveModelFileLocation.js'
|
|
6
|
+
|
|
7
|
+
interface ValidatableModelConfig {
|
|
8
|
+
url?: string
|
|
9
|
+
location?: string
|
|
10
|
+
modelsCachePath: string
|
|
11
|
+
sha256?: string
|
|
12
|
+
md5?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function validateModelFile(config: ValidatableModelConfig): Promise<string | undefined> {
|
|
16
|
+
const fileLocation = resolveModelFileLocation({
|
|
17
|
+
url: config.url,
|
|
18
|
+
filePath: config.location,
|
|
19
|
+
modelsCachePath: config.modelsCachePath,
|
|
20
|
+
})
|
|
21
|
+
if (!fs.existsSync(fileLocation)) {
|
|
22
|
+
return `Model file missing at ${fileLocation}`
|
|
23
|
+
}
|
|
24
|
+
const hasChecksum = config.sha256 || config.md5
|
|
25
|
+
let validatedChecksum = false
|
|
26
|
+
const validateChecksum = async () => {
|
|
27
|
+
if (config.md5) {
|
|
28
|
+
const fileHash = await calculateFileChecksum(fileLocation, 'md5')
|
|
29
|
+
if (fileHash === config.md5) {
|
|
30
|
+
validatedChecksum = true
|
|
31
|
+
return true
|
|
32
|
+
}
|
|
33
|
+
} else if (config.sha256) {
|
|
34
|
+
const fileHash = await calculateFileChecksum(fileLocation, 'sha256')
|
|
35
|
+
if (fileHash === config.sha256) {
|
|
36
|
+
validatedChecksum = true
|
|
37
|
+
return true
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
const ipullFile = fileLocation + '.ipull'
|
|
43
|
+
if (fs.existsSync(ipullFile)) {
|
|
44
|
+
// if we have a valid file at the download destination, we can remove the ipull file
|
|
45
|
+
if (hasChecksum) {
|
|
46
|
+
const isValid = await validateChecksum()
|
|
47
|
+
if (isValid) {
|
|
48
|
+
fs.unlinkSync(ipullFile)
|
|
49
|
+
validatedChecksum = true
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!validatedChecksum) {
|
|
53
|
+
return `Model file with incomplete download`
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!validatedChecksum && hasChecksum) {
|
|
58
|
+
if (config.sha256) {
|
|
59
|
+
const fileHash = await calculateFileChecksum(fileLocation, 'sha256')
|
|
60
|
+
if (fileHash !== config.sha256) {
|
|
61
|
+
return `File sha256 checksum mismatch: expected ${config.sha256} got ${fileHash} at ${fileLocation}`
|
|
62
|
+
}
|
|
63
|
+
} else if (config.md5) {
|
|
64
|
+
const fileHash = await calculateFileChecksum(fileLocation, 'md5')
|
|
65
|
+
if (fileHash !== config.md5) {
|
|
66
|
+
return `File md5 checksum mismatch: expected ${config.md5} got ${fileHash} at ${fileLocation}`
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return undefined
|
|
71
|
+
}
|