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.
Files changed (227) hide show
  1. package/README.md +216 -0
  2. package/dist/api/openai/enums.d.ts +4 -0
  3. package/dist/api/openai/enums.js +17 -0
  4. package/dist/api/openai/enums.js.map +1 -0
  5. package/dist/api/openai/handlers/chat.d.ts +3 -0
  6. package/dist/api/openai/handlers/chat.js +358 -0
  7. package/dist/api/openai/handlers/chat.js.map +1 -0
  8. package/dist/api/openai/handlers/completions.d.ts +3 -0
  9. package/dist/api/openai/handlers/completions.js +169 -0
  10. package/dist/api/openai/handlers/completions.js.map +1 -0
  11. package/dist/api/openai/handlers/embeddings.d.ts +3 -0
  12. package/dist/api/openai/handlers/embeddings.js +74 -0
  13. package/dist/api/openai/handlers/embeddings.js.map +1 -0
  14. package/dist/api/openai/handlers/images.d.ts +0 -0
  15. package/dist/api/openai/handlers/images.js +4 -0
  16. package/dist/api/openai/handlers/images.js.map +1 -0
  17. package/dist/api/openai/handlers/models.d.ts +3 -0
  18. package/dist/api/openai/handlers/models.js +23 -0
  19. package/dist/api/openai/handlers/models.js.map +1 -0
  20. package/dist/api/openai/handlers/transcription.d.ts +0 -0
  21. package/dist/api/openai/handlers/transcription.js +4 -0
  22. package/dist/api/openai/handlers/transcription.js.map +1 -0
  23. package/dist/api/openai/index.d.ts +7 -0
  24. package/dist/api/openai/index.js +14 -0
  25. package/dist/api/openai/index.js.map +1 -0
  26. package/dist/api/parseJSONRequestBody.d.ts +2 -0
  27. package/dist/api/parseJSONRequestBody.js +24 -0
  28. package/dist/api/parseJSONRequestBody.js.map +1 -0
  29. package/dist/api/v1/index.d.ts +2 -0
  30. package/dist/api/v1/index.js +29 -0
  31. package/dist/api/v1/index.js.map +1 -0
  32. package/dist/cli.d.ts +1 -0
  33. package/dist/cli.js +10 -0
  34. package/dist/cli.js.map +1 -0
  35. package/dist/engines/gpt4all/engine.d.ts +34 -0
  36. package/dist/engines/gpt4all/engine.js +357 -0
  37. package/dist/engines/gpt4all/engine.js.map +1 -0
  38. package/dist/engines/gpt4all/util.d.ts +3 -0
  39. package/dist/engines/gpt4all/util.js +29 -0
  40. package/dist/engines/gpt4all/util.js.map +1 -0
  41. package/dist/engines/index.d.ts +19 -0
  42. package/dist/engines/index.js +21 -0
  43. package/dist/engines/index.js.map +1 -0
  44. package/dist/engines/node-llama-cpp/engine.d.ts +49 -0
  45. package/dist/engines/node-llama-cpp/engine.js +666 -0
  46. package/dist/engines/node-llama-cpp/engine.js.map +1 -0
  47. package/dist/engines/node-llama-cpp/types.d.ts +13 -0
  48. package/dist/engines/node-llama-cpp/types.js +2 -0
  49. package/dist/engines/node-llama-cpp/types.js.map +1 -0
  50. package/dist/engines/node-llama-cpp/util.d.ts +15 -0
  51. package/dist/engines/node-llama-cpp/util.js +84 -0
  52. package/dist/engines/node-llama-cpp/util.js.map +1 -0
  53. package/dist/engines/node-llama-cpp/validateModelFile.d.ts +8 -0
  54. package/dist/engines/node-llama-cpp/validateModelFile.js +36 -0
  55. package/dist/engines/node-llama-cpp/validateModelFile.js.map +1 -0
  56. package/dist/engines/stable-diffusion-cpp/engine.d.ts +90 -0
  57. package/dist/engines/stable-diffusion-cpp/engine.js +294 -0
  58. package/dist/engines/stable-diffusion-cpp/engine.js.map +1 -0
  59. package/dist/engines/stable-diffusion-cpp/types.d.ts +3 -0
  60. package/dist/engines/stable-diffusion-cpp/types.js +2 -0
  61. package/dist/engines/stable-diffusion-cpp/types.js.map +1 -0
  62. package/dist/engines/stable-diffusion-cpp/util.d.ts +4 -0
  63. package/dist/engines/stable-diffusion-cpp/util.js +55 -0
  64. package/dist/engines/stable-diffusion-cpp/util.js.map +1 -0
  65. package/dist/engines/stable-diffusion-cpp/validateModelFiles.d.ts +19 -0
  66. package/dist/engines/stable-diffusion-cpp/validateModelFiles.js +91 -0
  67. package/dist/engines/stable-diffusion-cpp/validateModelFiles.js.map +1 -0
  68. package/dist/engines/transformers-js/engine.d.ts +37 -0
  69. package/dist/engines/transformers-js/engine.js +538 -0
  70. package/dist/engines/transformers-js/engine.js.map +1 -0
  71. package/dist/engines/transformers-js/types.d.ts +7 -0
  72. package/dist/engines/transformers-js/types.js +2 -0
  73. package/dist/engines/transformers-js/types.js.map +1 -0
  74. package/dist/engines/transformers-js/util.d.ts +7 -0
  75. package/dist/engines/transformers-js/util.js +36 -0
  76. package/dist/engines/transformers-js/util.js.map +1 -0
  77. package/dist/engines/transformers-js/validateModelFiles.d.ts +17 -0
  78. package/dist/engines/transformers-js/validateModelFiles.js +133 -0
  79. package/dist/engines/transformers-js/validateModelFiles.js.map +1 -0
  80. package/dist/experiments/ChatWithVision.d.ts +11 -0
  81. package/dist/experiments/ChatWithVision.js +91 -0
  82. package/dist/experiments/ChatWithVision.js.map +1 -0
  83. package/dist/experiments/StableDiffPromptGenerator.d.ts +0 -0
  84. package/dist/experiments/StableDiffPromptGenerator.js +4 -0
  85. package/dist/experiments/StableDiffPromptGenerator.js.map +1 -0
  86. package/dist/experiments/VoiceFunctionCall.d.ts +18 -0
  87. package/dist/experiments/VoiceFunctionCall.js +51 -0
  88. package/dist/experiments/VoiceFunctionCall.js.map +1 -0
  89. package/dist/http.d.ts +19 -0
  90. package/dist/http.js +54 -0
  91. package/dist/http.js.map +1 -0
  92. package/dist/index.d.ts +7 -0
  93. package/dist/index.js +8 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/instance.d.ts +88 -0
  96. package/dist/instance.js +594 -0
  97. package/dist/instance.js.map +1 -0
  98. package/dist/lib/acquireFileLock.d.ts +7 -0
  99. package/dist/lib/acquireFileLock.js +38 -0
  100. package/dist/lib/acquireFileLock.js.map +1 -0
  101. package/dist/lib/calculateContextIdentity.d.ts +7 -0
  102. package/dist/lib/calculateContextIdentity.js +39 -0
  103. package/dist/lib/calculateContextIdentity.js.map +1 -0
  104. package/dist/lib/calculateFileChecksum.d.ts +1 -0
  105. package/dist/lib/calculateFileChecksum.js +16 -0
  106. package/dist/lib/calculateFileChecksum.js.map +1 -0
  107. package/dist/lib/copyDirectory.d.ts +6 -0
  108. package/dist/lib/copyDirectory.js +27 -0
  109. package/dist/lib/copyDirectory.js.map +1 -0
  110. package/dist/lib/decodeAudio.d.ts +1 -0
  111. package/dist/lib/decodeAudio.js +26 -0
  112. package/dist/lib/decodeAudio.js.map +1 -0
  113. package/dist/lib/downloadModelFile.d.ts +10 -0
  114. package/dist/lib/downloadModelFile.js +58 -0
  115. package/dist/lib/downloadModelFile.js.map +1 -0
  116. package/dist/lib/flattenMessageTextContent.d.ts +2 -0
  117. package/dist/lib/flattenMessageTextContent.js +11 -0
  118. package/dist/lib/flattenMessageTextContent.js.map +1 -0
  119. package/dist/lib/getCacheDirPath.d.ts +12 -0
  120. package/dist/lib/getCacheDirPath.js +31 -0
  121. package/dist/lib/getCacheDirPath.js.map +1 -0
  122. package/dist/lib/loadImage.d.ts +12 -0
  123. package/dist/lib/loadImage.js +30 -0
  124. package/dist/lib/loadImage.js.map +1 -0
  125. package/dist/lib/logger.d.ts +12 -0
  126. package/dist/lib/logger.js +98 -0
  127. package/dist/lib/logger.js.map +1 -0
  128. package/dist/lib/math.d.ts +7 -0
  129. package/dist/lib/math.js +30 -0
  130. package/dist/lib/math.js.map +1 -0
  131. package/dist/lib/resolveModelFileLocation.d.ts +15 -0
  132. package/dist/lib/resolveModelFileLocation.js +41 -0
  133. package/dist/lib/resolveModelFileLocation.js.map +1 -0
  134. package/dist/lib/util.d.ts +7 -0
  135. package/dist/lib/util.js +61 -0
  136. package/dist/lib/util.js.map +1 -0
  137. package/dist/lib/validateModelFile.d.ts +9 -0
  138. package/dist/lib/validateModelFile.js +62 -0
  139. package/dist/lib/validateModelFile.js.map +1 -0
  140. package/dist/lib/validateModelOptions.d.ts +3 -0
  141. package/dist/lib/validateModelOptions.js +23 -0
  142. package/dist/lib/validateModelOptions.js.map +1 -0
  143. package/dist/pool.d.ts +61 -0
  144. package/dist/pool.js +512 -0
  145. package/dist/pool.js.map +1 -0
  146. package/dist/server.d.ts +59 -0
  147. package/dist/server.js +221 -0
  148. package/dist/server.js.map +1 -0
  149. package/dist/standalone.d.ts +1 -0
  150. package/dist/standalone.js +306 -0
  151. package/dist/standalone.js.map +1 -0
  152. package/dist/store.d.ts +60 -0
  153. package/dist/store.js +203 -0
  154. package/dist/store.js.map +1 -0
  155. package/dist/types/completions.d.ts +57 -0
  156. package/dist/types/completions.js +2 -0
  157. package/dist/types/completions.js.map +1 -0
  158. package/dist/types/index.d.ts +326 -0
  159. package/dist/types/index.js +2 -0
  160. package/dist/types/index.js.map +1 -0
  161. package/docs/engines.md +28 -0
  162. package/docs/gpu.md +72 -0
  163. package/docs/http-api.md +147 -0
  164. package/examples/all-options.js +108 -0
  165. package/examples/chat-cli.js +56 -0
  166. package/examples/chat-server.js +65 -0
  167. package/examples/concurrency.js +70 -0
  168. package/examples/express.js +70 -0
  169. package/examples/pool.js +91 -0
  170. package/package.json +113 -0
  171. package/src/api/openai/enums.ts +20 -0
  172. package/src/api/openai/handlers/chat.ts +408 -0
  173. package/src/api/openai/handlers/completions.ts +196 -0
  174. package/src/api/openai/handlers/embeddings.ts +92 -0
  175. package/src/api/openai/handlers/images.ts +3 -0
  176. package/src/api/openai/handlers/models.ts +33 -0
  177. package/src/api/openai/handlers/transcription.ts +2 -0
  178. package/src/api/openai/index.ts +16 -0
  179. package/src/api/parseJSONRequestBody.ts +26 -0
  180. package/src/api/v1/DRAFT.md +16 -0
  181. package/src/api/v1/index.ts +37 -0
  182. package/src/cli.ts +9 -0
  183. package/src/engines/gpt4all/engine.ts +441 -0
  184. package/src/engines/gpt4all/util.ts +31 -0
  185. package/src/engines/index.ts +28 -0
  186. package/src/engines/node-llama-cpp/engine.ts +811 -0
  187. package/src/engines/node-llama-cpp/types.ts +17 -0
  188. package/src/engines/node-llama-cpp/util.ts +126 -0
  189. package/src/engines/node-llama-cpp/validateModelFile.ts +46 -0
  190. package/src/engines/stable-diffusion-cpp/engine.ts +369 -0
  191. package/src/engines/stable-diffusion-cpp/types.ts +54 -0
  192. package/src/engines/stable-diffusion-cpp/util.ts +58 -0
  193. package/src/engines/stable-diffusion-cpp/validateModelFiles.ts +119 -0
  194. package/src/engines/transformers-js/engine.ts +659 -0
  195. package/src/engines/transformers-js/types.ts +25 -0
  196. package/src/engines/transformers-js/util.ts +40 -0
  197. package/src/engines/transformers-js/validateModelFiles.ts +168 -0
  198. package/src/experiments/ChatWithVision.ts +103 -0
  199. package/src/experiments/StableDiffPromptGenerator.ts +2 -0
  200. package/src/experiments/VoiceFunctionCall.ts +71 -0
  201. package/src/http.ts +72 -0
  202. package/src/index.ts +7 -0
  203. package/src/instance.ts +723 -0
  204. package/src/lib/acquireFileLock.ts +38 -0
  205. package/src/lib/calculateContextIdentity.ts +53 -0
  206. package/src/lib/calculateFileChecksum.ts +18 -0
  207. package/src/lib/copyDirectory.ts +29 -0
  208. package/src/lib/decodeAudio.ts +39 -0
  209. package/src/lib/downloadModelFile.ts +70 -0
  210. package/src/lib/flattenMessageTextContent.ts +19 -0
  211. package/src/lib/getCacheDirPath.ts +34 -0
  212. package/src/lib/loadImage.ts +46 -0
  213. package/src/lib/logger.ts +112 -0
  214. package/src/lib/math.ts +31 -0
  215. package/src/lib/resolveModelFileLocation.ts +49 -0
  216. package/src/lib/util.ts +75 -0
  217. package/src/lib/validateModelFile.ts +71 -0
  218. package/src/lib/validateModelOptions.ts +31 -0
  219. package/src/pool.ts +651 -0
  220. package/src/server.ts +270 -0
  221. package/src/standalone.ts +320 -0
  222. package/src/store.ts +278 -0
  223. package/src/types/completions.ts +86 -0
  224. package/src/types/index.ts +488 -0
  225. package/tsconfig.json +29 -0
  226. package/tsconfig.release.json +11 -0
  227. 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
+ }
@@ -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
+ }
@@ -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
+ }