offgrid-ai 0.11.0 → 0.11.1
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/package.json +1 -1
- package/src/backends.mjs +10 -6
- package/src/mlx-discovery.mjs +14 -13
- package/src/model-name.mjs +3 -0
package/package.json
CHANGED
package/src/backends.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { findLlamaServer } from "./config.mjs";
|
|
2
2
|
import { scanGgufModels } from "./scan.mjs";
|
|
3
3
|
import { parseModelName } from "./model-name.mjs";
|
|
4
|
-
import { scanMlxModels, scanOmlxModelSizes,
|
|
4
|
+
import { scanMlxModels, scanOmlxModelSizes, lookupOmlxModelInfo } from "./mlx-discovery.mjs";
|
|
5
5
|
import { DEFAULT_PORT as MLX_VLM_PORT } from "./mlx-flags.mjs";
|
|
6
6
|
|
|
7
7
|
// ── Backend definitions ────────────────────────────────────────────────────
|
|
@@ -96,19 +96,23 @@ async function scanOmlxModels() {
|
|
|
96
96
|
const body = await response.json();
|
|
97
97
|
if (!Array.isArray(body?.data)) return [];
|
|
98
98
|
|
|
99
|
-
// The oMLX API doesn't return model sizes — look them up from disk.
|
|
100
|
-
const
|
|
99
|
+
// The oMLX API doesn't return model sizes or publishers — look them up from disk.
|
|
100
|
+
const infoMap = await scanOmlxModelSizes();
|
|
101
101
|
|
|
102
102
|
return body.data
|
|
103
103
|
.filter((model) => isChatOmlxModel(model))
|
|
104
104
|
.map((model) => {
|
|
105
|
-
const
|
|
106
|
-
|
|
105
|
+
const info = lookupOmlxModelInfo(model.id, infoMap);
|
|
106
|
+
// If the API ID doesn't already include a publisher (no / or --),
|
|
107
|
+
// prepend the publisher found on disk.
|
|
108
|
+
const hasPublisher = model.id.includes("/") || model.id.includes("--");
|
|
109
|
+
const fullName = (!hasPublisher && info?.publisher) ? `${info.publisher}/${model.id}` : model.id;
|
|
110
|
+
const parsed = parseModelName(fullName, "omlx");
|
|
107
111
|
return {
|
|
108
112
|
id: model.id,
|
|
109
113
|
label: parsed.display,
|
|
110
114
|
aliasSuggestion: model.id,
|
|
111
|
-
sizeBytes:
|
|
115
|
+
sizeBytes: info?.sizeBytes ?? (model.size ?? 0),
|
|
112
116
|
contextLength: model.max_model_len ?? null,
|
|
113
117
|
quant: parsed.quant,
|
|
114
118
|
family: null,
|
package/src/mlx-discovery.mjs
CHANGED
|
@@ -308,14 +308,14 @@ export function defaultMlxContextLength(trainedCtx, ramGb) {
|
|
|
308
308
|
|
|
309
309
|
/**
|
|
310
310
|
* Scan the oMLX models directory (~/.omlx/models/) for MLX model directories
|
|
311
|
-
* and return a Map of basename → sizeBytes. The oMLX API
|
|
312
|
-
* sizes, so we compute them from
|
|
311
|
+
* and return a Map of basename → { sizeBytes, publisher }. The oMLX API
|
|
312
|
+
* doesn't return model sizes or publishers, so we compute them from disk.
|
|
313
313
|
*/
|
|
314
314
|
export async function scanOmlxModelSizes() {
|
|
315
315
|
if (!existsSync(OMLX_MODELS_DIR)) return new Map();
|
|
316
|
-
const
|
|
316
|
+
const infoByBasename = new Map();
|
|
317
317
|
|
|
318
|
-
async function walk(dir) {
|
|
318
|
+
async function walk(dir, publisher) {
|
|
319
319
|
let entries;
|
|
320
320
|
try {
|
|
321
321
|
entries = await readdir(dir, { withFileTypes: true });
|
|
@@ -327,26 +327,27 @@ export async function scanOmlxModelSizes() {
|
|
|
327
327
|
const fullPath = join(dir, entry.name);
|
|
328
328
|
if (await isMlxModelDir(fullPath)) {
|
|
329
329
|
const sizeBytes = await getMlxDirSizeBytes(fullPath);
|
|
330
|
-
if (sizeBytes > 0)
|
|
330
|
+
if (sizeBytes > 0) infoByBasename.set(entry.name, { sizeBytes, publisher });
|
|
331
331
|
} else {
|
|
332
|
-
|
|
332
|
+
// First-level directories under ~/.omlx/models/ are publishers
|
|
333
|
+
await walk(fullPath, publisher ?? entry.name);
|
|
333
334
|
}
|
|
334
335
|
}
|
|
335
336
|
}
|
|
336
337
|
|
|
337
|
-
await walk(OMLX_MODELS_DIR);
|
|
338
|
-
return
|
|
338
|
+
await walk(OMLX_MODELS_DIR, null);
|
|
339
|
+
return infoByBasename;
|
|
339
340
|
}
|
|
340
341
|
|
|
341
342
|
/**
|
|
342
|
-
* Look up a model's
|
|
343
|
+
* Look up a model's info by its oMLX API id. Tries exact match, then the
|
|
343
344
|
* segment after `--` (oMLX org--name format), then after `/` (HF format).
|
|
344
345
|
*/
|
|
345
|
-
export function
|
|
346
|
-
if (
|
|
346
|
+
export function lookupOmlxModelInfo(modelId, infoMap) {
|
|
347
|
+
if (infoMap.has(modelId)) return infoMap.get(modelId);
|
|
347
348
|
const dashIdx = modelId.indexOf("--");
|
|
348
|
-
if (dashIdx >= 0 &&
|
|
349
|
+
if (dashIdx >= 0 && infoMap.has(modelId.slice(dashIdx + 2))) return infoMap.get(modelId.slice(dashIdx + 2));
|
|
349
350
|
const slashIdx = modelId.indexOf("/");
|
|
350
|
-
if (slashIdx >= 0 &&
|
|
351
|
+
if (slashIdx >= 0 && infoMap.has(modelId.slice(slashIdx + 1))) return infoMap.get(modelId.slice(slashIdx + 1));
|
|
351
352
|
return null;
|
|
352
353
|
}
|
package/src/model-name.mjs
CHANGED
|
@@ -195,6 +195,9 @@ function titleCaseModel(name) {
|
|
|
195
195
|
// Title-case standalone aXb patterns (A3B, A12B — active parameters)
|
|
196
196
|
result = result.replace(/\ba(\d+)\s*b\b/gi, (_, num) => `A${num}B`);
|
|
197
197
|
|
|
198
|
+
// Title-case eXb patterns (E2B, E12B — effective parameters)
|
|
199
|
+
result = result.replace(/\be(\d+)\s*b\b/gi, (_, num) => `E${num}B`);
|
|
200
|
+
|
|
198
201
|
// Clean up extra spaces
|
|
199
202
|
result = result.replace(/\s{2,}/g, " ").trim();
|
|
200
203
|
|