@tiens.nguyen/gonext-local-worker 1.0.11 → 1.0.13
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/gonext-local-worker.mjs +109 -13
- package/package.json +1 -1
package/gonext-local-worker.mjs
CHANGED
|
@@ -6,9 +6,14 @@
|
|
|
6
6
|
* - `gonext-local-worker` starts polling loop
|
|
7
7
|
*/
|
|
8
8
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
9
|
-
import {
|
|
9
|
+
import { execFile as execFileCallback } from "node:child_process";
|
|
10
|
+
import { homedir, platform } from "node:os";
|
|
10
11
|
import { join } from "node:path";
|
|
12
|
+
import { promisify } from "node:util";
|
|
11
13
|
import dotenv from "dotenv";
|
|
14
|
+
|
|
15
|
+
/** Avoid `node:child_process/promises` — not available on some Node builds / older runtimes. */
|
|
16
|
+
const execFile = promisify(execFileCallback);
|
|
12
17
|
import OpenAI from "openai";
|
|
13
18
|
|
|
14
19
|
const ENV_FILE = join(homedir(), ".gonext", "worker.env");
|
|
@@ -28,6 +33,9 @@ Usage:
|
|
|
28
33
|
Examples:
|
|
29
34
|
gonext-local-worker set abc123 --api-base https://hwohu56e8d.execute-api.ap-southeast-1.amazonaws.com
|
|
30
35
|
gonext-local-worker
|
|
36
|
+
|
|
37
|
+
Env (optional):
|
|
38
|
+
GONEXT_MLX_LM_PYTHON Python executable for MLX LM native probe (default: python3)
|
|
31
39
|
`);
|
|
32
40
|
}
|
|
33
41
|
|
|
@@ -228,6 +236,50 @@ async function checkOpenAiModels(base, apiKey) {
|
|
|
228
236
|
}
|
|
229
237
|
}
|
|
230
238
|
|
|
239
|
+
/** True MLX LM check: import mlx_lm in Python (macOS). Not the OpenAI HTTP surface. */
|
|
240
|
+
async function checkMlxLmNativeImport() {
|
|
241
|
+
const preferred = (process.env.GONEXT_MLX_LM_PYTHON ?? "").trim() || "python3";
|
|
242
|
+
const code = [
|
|
243
|
+
"import sys",
|
|
244
|
+
"try:",
|
|
245
|
+
" import mlx_lm",
|
|
246
|
+
" v = getattr(mlx_lm, '__version__', None)",
|
|
247
|
+
" print(v or 'ok')",
|
|
248
|
+
"except Exception:",
|
|
249
|
+
" sys.exit(1)",
|
|
250
|
+
].join("\n");
|
|
251
|
+
|
|
252
|
+
const candidates = [preferred];
|
|
253
|
+
if (preferred === "python3") candidates.push("python");
|
|
254
|
+
|
|
255
|
+
const tried = [];
|
|
256
|
+
for (const exe of [...new Set(candidates)]) {
|
|
257
|
+
tried.push(exe);
|
|
258
|
+
try {
|
|
259
|
+
const { stdout } = await execFile(exe, ["-c", code], {
|
|
260
|
+
timeout: 15000,
|
|
261
|
+
maxBuffer: 65536,
|
|
262
|
+
windowsHide: true,
|
|
263
|
+
});
|
|
264
|
+
const version = String(stdout ?? "").trim();
|
|
265
|
+
return {
|
|
266
|
+
available: true,
|
|
267
|
+
python: exe,
|
|
268
|
+
version: version || undefined,
|
|
269
|
+
method: "python_import_mlx_lm",
|
|
270
|
+
};
|
|
271
|
+
} catch {
|
|
272
|
+
/* try next */
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
available: false,
|
|
277
|
+
python: preferred,
|
|
278
|
+
method: "python_import_mlx_lm",
|
|
279
|
+
error: `Could not import mlx_lm (tried: ${tried.join(", ")})`,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
231
283
|
async function runLocalHealthJob(job) {
|
|
232
284
|
const { jobId, payload } = job;
|
|
233
285
|
const start = Date.now();
|
|
@@ -269,15 +321,66 @@ async function runLocalHealthJob(job) {
|
|
|
269
321
|
}
|
|
270
322
|
}
|
|
271
323
|
const mlxRoot = normalizeOpenAiV1Root(payload?.mlxOpenAiBaseUrl);
|
|
272
|
-
let
|
|
324
|
+
let mlxHttp = null;
|
|
325
|
+
let mlxNative = null;
|
|
326
|
+
|
|
273
327
|
if (mlxRoot) {
|
|
274
328
|
const mlxStart = Date.now();
|
|
275
|
-
console.log(`[gonext-worker] local_health ${jobId} check mlx ${mlxRoot}`);
|
|
276
|
-
|
|
329
|
+
console.log(`[gonext-worker] local_health ${jobId} check mlx HTTP ${mlxRoot}`);
|
|
330
|
+
mlxHttp = await checkOpenAiModels(mlxRoot, payload?.mlxApiKey ?? "");
|
|
277
331
|
console.log(
|
|
278
|
-
`[gonext-worker] local_health ${jobId} mlx
|
|
332
|
+
`[gonext-worker] local_health ${jobId} mlx HTTP online=${mlxHttp.online} models=${mlxHttp.models.length} took=${((Date.now() - mlxStart) / 1000).toFixed(2)}s`
|
|
279
333
|
);
|
|
280
334
|
}
|
|
335
|
+
|
|
336
|
+
const wantNativeFallback =
|
|
337
|
+
mlxRoot &&
|
|
338
|
+
payload?.mlxNativeFallback !== false &&
|
|
339
|
+
platform() === "darwin" &&
|
|
340
|
+
(!mlxHttp?.online || (mlxHttp?.models?.length ?? 0) === 0);
|
|
341
|
+
|
|
342
|
+
if (wantNativeFallback) {
|
|
343
|
+
const t0 = Date.now();
|
|
344
|
+
console.log(
|
|
345
|
+
`[gonext-worker] local_health ${jobId} mlx native probe (Python mlx_lm import)`
|
|
346
|
+
);
|
|
347
|
+
mlxNative = await checkMlxLmNativeImport();
|
|
348
|
+
console.log(
|
|
349
|
+
`[gonext-worker] local_health ${jobId} mlx native available=${mlxNative.available} took=${((Date.now() - t0) / 1000).toFixed(2)}s`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
let mlx = null;
|
|
354
|
+
if (mlxRoot || mlxNative?.available) {
|
|
355
|
+
const httpOk = Boolean(mlxHttp?.online && (mlxHttp?.models?.length ?? 0) > 0);
|
|
356
|
+
const nativeOk = mlxNative?.available === true;
|
|
357
|
+
mlx = {
|
|
358
|
+
configured: httpOk || nativeOk,
|
|
359
|
+
online: httpOk || nativeOk,
|
|
360
|
+
models: httpOk
|
|
361
|
+
? mlxHttp.models
|
|
362
|
+
: nativeOk
|
|
363
|
+
? [
|
|
364
|
+
{
|
|
365
|
+
id: "mlx_lm_native",
|
|
366
|
+
name: mlxNative.version
|
|
367
|
+
? `MLX LM (${mlxNative.version})`
|
|
368
|
+
: "MLX LM (Python import OK)",
|
|
369
|
+
value: "mlx:mlx_lm_native",
|
|
370
|
+
},
|
|
371
|
+
]
|
|
372
|
+
: [],
|
|
373
|
+
endpoint: mlxHttp?.endpoint,
|
|
374
|
+
http: mlxHttp
|
|
375
|
+
? {
|
|
376
|
+
online: mlxHttp.online,
|
|
377
|
+
endpoint: mlxHttp.endpoint,
|
|
378
|
+
modelCount: mlxHttp.models.length,
|
|
379
|
+
}
|
|
380
|
+
: undefined,
|
|
381
|
+
native: mlxNative ?? undefined,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
281
384
|
const result = {
|
|
282
385
|
ollama:
|
|
283
386
|
ollamaBases.length > 0
|
|
@@ -289,14 +392,7 @@ async function runLocalHealthJob(job) {
|
|
|
289
392
|
sources: ollamaSources,
|
|
290
393
|
}
|
|
291
394
|
: undefined,
|
|
292
|
-
mlx
|
|
293
|
-
? {
|
|
294
|
-
configured: mlx.models.length > 0,
|
|
295
|
-
online: mlx.online,
|
|
296
|
-
models: mlx.models,
|
|
297
|
-
endpoint: mlx.endpoint,
|
|
298
|
-
}
|
|
299
|
-
: undefined,
|
|
395
|
+
mlx,
|
|
300
396
|
};
|
|
301
397
|
const totalTimeSeconds = (Date.now() - start) / 1000;
|
|
302
398
|
await workerFetch(`/api/worker/jobs/${jobId}`, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiens.nguyen/gonext-local-worker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "Polls GoNext cloud API for async local LLM jobs and runs them against Ollama/OpenAI-compatible servers on this Mac",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|