@ai-matrx/agents 0.59.268 → 0.59.270
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/CHANGELOG.md +11 -1
- package/dist/context/index.cjs +11 -0
- package/dist/context/index.d.cts +9 -1
- package/dist/context/index.d.ts +9 -1
- package/dist/context/index.js +11 -0
- package/dist/context/react/index.cjs +22 -6
- package/dist/context/react/index.d.cts +8 -1
- package/dist/context/react/index.d.ts +8 -1
- package/dist/context/react/index.js +25 -8
- package/dist/models/index.cjs +368 -238
- package/dist/models/index.d.cts +148 -14
- package/dist/models/index.d.ts +148 -14
- package/dist/models/index.js +368 -238
- package/dist/models/react/index.d.cts +9 -1
- package/dist/models/react/index.d.ts +9 -1
- package/package.json +2 -20
- package/scripts/check-model-catalog-reads.mjs +11 -6
package/dist/models/index.cjs
CHANGED
|
@@ -23,6 +23,12 @@ __export(models_exports, {
|
|
|
23
23
|
CHAT_DEFAULT_MODEL_KNOB: () => CHAT_DEFAULT_MODEL_KNOB,
|
|
24
24
|
CONTENT_TYPES: () => CONTENT_TYPES,
|
|
25
25
|
DEFAULT_CAPABILITIES: () => DEFAULT_CAPABILITIES,
|
|
26
|
+
DEVICE_FILE_USABLE_STATES: () => DEVICE_FILE_USABLE_STATES,
|
|
27
|
+
DEVICE_OWN_FILE_USABLE_STATES: () => DEVICE_OWN_FILE_USABLE_STATES,
|
|
28
|
+
DEVICE_RULE_TWIN_FIXTURE: () => DEVICE_RULE_TWIN_FIXTURE,
|
|
29
|
+
DEVICE_SERVED_SETTINGS_KEY: () => DEVICE_SERVED_SETTINGS_KEY,
|
|
30
|
+
DEVICE_SERVED_VIA: () => DEVICE_SERVED_VIA,
|
|
31
|
+
DeviceModelUnavailableError: () => DeviceModelUnavailableError,
|
|
26
32
|
EMPTY_MODEL_RECORDS: () => EMPTY_MODEL_RECORDS,
|
|
27
33
|
EXCLUDED_REVIEW_STATES: () => EXCLUDED_REVIEW_STATES,
|
|
28
34
|
FEATURE_BUCKETS: () => FEATURE_BUCKETS,
|
|
@@ -43,6 +49,7 @@ __export(models_exports, {
|
|
|
43
49
|
TOP_TIER_LOCKED_TITLE: () => TOP_TIER_LOCKED_TITLE,
|
|
44
50
|
acceptsTextInstructions: () => acceptsTextInstructions,
|
|
45
51
|
announceModelCatalogChanged: () => announceModelCatalogChanged,
|
|
52
|
+
availableOnDevices: () => availableOnDevices,
|
|
46
53
|
blendedPrice: () => blendedPrice,
|
|
47
54
|
buildModelClassIndex: () => buildModelClassIndex,
|
|
48
55
|
buildSuccessionIndex: () => buildSuccessionIndex,
|
|
@@ -112,6 +119,7 @@ __export(models_exports, {
|
|
|
112
119
|
quantFromName: () => quantFromName,
|
|
113
120
|
rankVariants: () => rankVariants,
|
|
114
121
|
readCuratedRows: () => readCuratedRows,
|
|
122
|
+
readDeviceModelFiles: () => readDeviceModelFiles,
|
|
115
123
|
readGgufHeader: () => readGgufHeader,
|
|
116
124
|
readModelCatalog: () => readModelCatalog,
|
|
117
125
|
readModelIdentities: () => readModelIdentities,
|
|
@@ -119,8 +127,10 @@ __export(models_exports, {
|
|
|
119
127
|
reconcileModelFavoriteIds: () => reconcileModelFavoriteIds,
|
|
120
128
|
reportModelDiagnostic: () => reportModelDiagnostic,
|
|
121
129
|
requireCanonicalCapabilities: () => requireCanonicalCapabilities,
|
|
130
|
+
requiredArtifactIds: () => requiredArtifactIds,
|
|
122
131
|
requiredArtifacts: () => requiredArtifacts,
|
|
123
132
|
resetSuccessionLogForTests: () => resetSuccessionLogForTests,
|
|
133
|
+
resolveDeviceModel: () => resolveDeviceModel,
|
|
124
134
|
resolveLiveModelId: () => resolveLiveModelId,
|
|
125
135
|
resolveModelMapKnob: () => resolveModelMapKnob,
|
|
126
136
|
resolveNewAgentModelId: () => resolveNewAgentModelId,
|
|
@@ -159,6 +169,7 @@ __export(models_exports, {
|
|
|
159
169
|
splitSetProblems: () => splitSetProblems,
|
|
160
170
|
takesInput: () => takesInput,
|
|
161
171
|
toAuditRecord: () => toAuditRecord,
|
|
172
|
+
toDeviceModelFile: () => toDeviceModelFile,
|
|
162
173
|
validateModelSet: () => validateModelSet,
|
|
163
174
|
variantVerdict: () => variantVerdict,
|
|
164
175
|
withModelClass: () => withModelClass
|
|
@@ -414,13 +425,13 @@ function setModelDiagnosticsSink(sink) {
|
|
|
414
425
|
}
|
|
415
426
|
function reportModelDiagnostic(dedupeKey, entry) {
|
|
416
427
|
try {
|
|
417
|
-
const
|
|
418
|
-
if (
|
|
419
|
-
|
|
428
|
+
const s4 = state();
|
|
429
|
+
if (s4.reported.has(dedupeKey)) return;
|
|
430
|
+
s4.reported.add(dedupeKey);
|
|
420
431
|
console.warn(`[parseCapabilities] ${entry.message}`);
|
|
421
|
-
if (
|
|
422
|
-
else if (!
|
|
423
|
-
|
|
432
|
+
if (s4.sink) s4.sink(entry);
|
|
433
|
+
else if (!s4.announced) {
|
|
434
|
+
s4.announced = true;
|
|
424
435
|
console.error(
|
|
425
436
|
"[@ai-matrx/agents/models] No diagnostics sink is bound \u2014 capability-vocabulary drift reaches the console only. Call setModelDiagnosticsSink(...) with your error store."
|
|
426
437
|
);
|
|
@@ -1093,13 +1104,13 @@ function createModelRecords(config) {
|
|
|
1093
1104
|
if (known.fetchScope === "active" || known.fetchScope === "all") {
|
|
1094
1105
|
jobs.push(
|
|
1095
1106
|
readOptions().then(
|
|
1096
|
-
(rows3) => update((
|
|
1097
|
-
const entities = { ...
|
|
1107
|
+
(rows3) => update((s4) => {
|
|
1108
|
+
const entities = { ...s4.entities };
|
|
1098
1109
|
for (const row of rows3) {
|
|
1099
1110
|
const existing = entities[row.id];
|
|
1100
1111
|
entities[row.id] = { ...emptyRecord(), ...existing, ...row, _fetchType: existing?._fetchType === "full" ? "full" : "options" };
|
|
1101
1112
|
}
|
|
1102
|
-
return withIdLists({ ...
|
|
1113
|
+
return withIdLists({ ...s4, entities, lastFetched: Date.now(), error: null });
|
|
1103
1114
|
}),
|
|
1104
1115
|
(err) => fail("model-records-refresh-options-failed", messageOf(err))
|
|
1105
1116
|
)
|
|
@@ -1109,9 +1120,9 @@ function createModelRecords(config) {
|
|
|
1109
1120
|
if (record._fetchType !== "full") continue;
|
|
1110
1121
|
jobs.push(
|
|
1111
1122
|
readFullModel(id).then(
|
|
1112
|
-
(fresh) => update((
|
|
1113
|
-
const existing =
|
|
1114
|
-
return withIdLists({ ...
|
|
1123
|
+
(fresh) => update((s4) => {
|
|
1124
|
+
const existing = s4.entities[fresh.id];
|
|
1125
|
+
return withIdLists({ ...s4, entities: { ...s4.entities, [fresh.id]: { ...emptyRecord(), ...existing, ...fresh, maker: fresh.maker ?? existing?.maker ?? null, _fetchType: "full" } } });
|
|
1115
1126
|
}),
|
|
1116
1127
|
(err) => fail("model-records-refresh-model-failed", `${id}: ${messageOf(err)}`, { modelId: id })
|
|
1117
1128
|
)
|
|
@@ -1122,7 +1133,7 @@ function createModelRecords(config) {
|
|
|
1122
1133
|
Promise.resolve(ai().from("model_definition").select("id, name, common_name, is_deprecated").eq(catalogKeyColumn(id), id).maybeSingle()).then(
|
|
1123
1134
|
({ data, error }) => {
|
|
1124
1135
|
if (error) throw error;
|
|
1125
|
-
if (data) update((
|
|
1136
|
+
if (data) update((s4) => ({ ...s4, identityById: { ...s4.identityById, [id]: data } }));
|
|
1126
1137
|
}
|
|
1127
1138
|
).catch((err) => fail("model-records-refresh-identity-failed", `${id}: ${messageOf(err)}`, { modelId: id }))
|
|
1128
1139
|
);
|
|
@@ -1130,7 +1141,7 @@ function createModelRecords(config) {
|
|
|
1130
1141
|
if (known.modelClassStatus === "succeeded") {
|
|
1131
1142
|
jobs.push(
|
|
1132
1143
|
readOfferings().then(
|
|
1133
|
-
(rows3) => update((
|
|
1144
|
+
(rows3) => update((s4) => ({ ...s4, modelClassIndex: buildModelClassIndex(rows3) })),
|
|
1134
1145
|
(err) => fail("model-records-refresh-classes-failed", messageOf(err))
|
|
1135
1146
|
)
|
|
1136
1147
|
);
|
|
@@ -1138,11 +1149,11 @@ function createModelRecords(config) {
|
|
|
1138
1149
|
for (const id of classesReadFor) {
|
|
1139
1150
|
jobs.push(
|
|
1140
1151
|
readOfferings(id).then(
|
|
1141
|
-
(rows3) => update((
|
|
1142
|
-
if (
|
|
1152
|
+
(rows3) => update((s4) => {
|
|
1153
|
+
if (s4.modelClassStatus === "succeeded") return s4;
|
|
1143
1154
|
const slice = buildModelClassIndex(rows3);
|
|
1144
|
-
const base =
|
|
1145
|
-
return { ...
|
|
1155
|
+
const base = s4.modelClassIndex ?? { classesByModelId: {}, classOfOffering: {} };
|
|
1156
|
+
return { ...s4, modelClassIndex: { classesByModelId: { ...base.classesByModelId, ...slice.classesByModelId }, classOfOffering: { ...base.classOfOffering, ...slice.classOfOffering } } };
|
|
1146
1157
|
}),
|
|
1147
1158
|
(err) => fail("model-records-refresh-classes-failed", `${id}: ${messageOf(err)}`, { modelId: id })
|
|
1148
1159
|
)
|
|
@@ -1156,7 +1167,7 @@ function createModelRecords(config) {
|
|
|
1156
1167
|
({ data, error }) => {
|
|
1157
1168
|
if (error) throw error;
|
|
1158
1169
|
const next = data && typeof data === "object" && !Array.isArray(data) ? data : null;
|
|
1159
|
-
update((
|
|
1170
|
+
update((s4) => ({ ...s4, classConfigByOffering: { ...s4.classConfigByOffering, [key]: { modelId: cfg.modelId, controls: next?.controls ?? null, constraints: next?.constraints ?? null } } }));
|
|
1160
1171
|
}
|
|
1161
1172
|
).catch((err) => fail("model-records-refresh-class-config-failed", `${key}: ${messageOf(err)}`))
|
|
1162
1173
|
);
|
|
@@ -1202,29 +1213,29 @@ function createModelRecords(config) {
|
|
|
1202
1213
|
loadOptions() {
|
|
1203
1214
|
if (state2.fetchScope === "active" || state2.fetchScope === "all") return Promise.resolve();
|
|
1204
1215
|
return once("options", async () => {
|
|
1205
|
-
update((
|
|
1216
|
+
update((s4) => ({ ...s4, isLoading: true, error: null }));
|
|
1206
1217
|
try {
|
|
1207
1218
|
const rows3 = await readOptions();
|
|
1208
|
-
update((
|
|
1209
|
-
const entities = { ...
|
|
1219
|
+
update((s4) => {
|
|
1220
|
+
const entities = { ...s4.entities };
|
|
1210
1221
|
for (const row of rows3) {
|
|
1211
1222
|
const existing = entities[row.id];
|
|
1212
1223
|
if (existing?._fetchType === "full") continue;
|
|
1213
1224
|
entities[row.id] = { ...emptyRecord(), ...existing, ...row, _fetchType: "options" };
|
|
1214
1225
|
}
|
|
1215
1226
|
return withIdLists({
|
|
1216
|
-
...
|
|
1227
|
+
...s4,
|
|
1217
1228
|
entities,
|
|
1218
1229
|
isLoading: false,
|
|
1219
1230
|
error: null,
|
|
1220
|
-
fetchScope:
|
|
1231
|
+
fetchScope: s4.fetchScope !== "all" && s4.fetchScope !== "deprecated" ? "active" : s4.fetchScope,
|
|
1221
1232
|
lastFetched: Date.now()
|
|
1222
1233
|
});
|
|
1223
1234
|
});
|
|
1224
1235
|
} catch (err) {
|
|
1225
1236
|
const message = messageOf(err);
|
|
1226
1237
|
errorSink({ code: "model-records-options-load-failed", message });
|
|
1227
|
-
update((
|
|
1238
|
+
update((s4) => ({ ...s4, isLoading: false, error: message }));
|
|
1228
1239
|
}
|
|
1229
1240
|
});
|
|
1230
1241
|
},
|
|
@@ -1234,23 +1245,23 @@ function createModelRecords(config) {
|
|
|
1234
1245
|
return inFlight.get(`model:${modelId}`) ?? Promise.resolve();
|
|
1235
1246
|
}
|
|
1236
1247
|
return once(`model:${modelId}`, async () => {
|
|
1237
|
-
update((
|
|
1238
|
-
const detailErrorById = { ...
|
|
1248
|
+
update((s4) => {
|
|
1249
|
+
const detailErrorById = { ...s4.detailErrorById };
|
|
1239
1250
|
delete detailErrorById[modelId];
|
|
1240
|
-
return { ...
|
|
1251
|
+
return { ...s4, detailStatusById: { ...s4.detailStatusById, [modelId]: "loading" }, detailErrorById };
|
|
1241
1252
|
});
|
|
1242
1253
|
try {
|
|
1243
1254
|
const record = await readFullModel(modelId);
|
|
1244
|
-
update((
|
|
1245
|
-
const existing =
|
|
1246
|
-
const detailErrorById = { ...
|
|
1255
|
+
update((s4) => {
|
|
1256
|
+
const existing = s4.entities[record.id];
|
|
1257
|
+
const detailErrorById = { ...s4.detailErrorById };
|
|
1247
1258
|
delete detailErrorById[modelId];
|
|
1248
1259
|
return withIdLists({
|
|
1249
|
-
...
|
|
1250
|
-
detailStatusById: { ...
|
|
1260
|
+
...s4,
|
|
1261
|
+
detailStatusById: { ...s4.detailStatusById, [modelId]: "succeeded" },
|
|
1251
1262
|
detailErrorById,
|
|
1252
1263
|
entities: {
|
|
1253
|
-
...
|
|
1264
|
+
...s4.entities,
|
|
1254
1265
|
[record.id]: { ...emptyRecord(), ...existing, ...record, maker: record.maker ?? existing?.maker ?? null, _fetchType: "full" }
|
|
1255
1266
|
}
|
|
1256
1267
|
});
|
|
@@ -1258,21 +1269,21 @@ function createModelRecords(config) {
|
|
|
1258
1269
|
} catch (err) {
|
|
1259
1270
|
const message = `Model configuration ${modelId}: ${messageOf(err)}`;
|
|
1260
1271
|
errorSink({ code: "model-records-model-load-failed", message, context: { modelId } });
|
|
1261
|
-
update((
|
|
1262
|
-
...
|
|
1263
|
-
detailStatusById: { ...
|
|
1264
|
-
detailErrorById: { ...
|
|
1272
|
+
update((s4) => ({
|
|
1273
|
+
...s4,
|
|
1274
|
+
detailStatusById: { ...s4.detailStatusById, [modelId]: "failed" },
|
|
1275
|
+
detailErrorById: { ...s4.detailErrorById, [modelId]: message }
|
|
1265
1276
|
}));
|
|
1266
1277
|
}
|
|
1267
1278
|
});
|
|
1268
1279
|
},
|
|
1269
1280
|
retryModel(modelId) {
|
|
1270
|
-
update((
|
|
1271
|
-
const detailStatusById = { ...
|
|
1272
|
-
const detailErrorById = { ...
|
|
1281
|
+
update((s4) => {
|
|
1282
|
+
const detailStatusById = { ...s4.detailStatusById };
|
|
1283
|
+
const detailErrorById = { ...s4.detailErrorById };
|
|
1273
1284
|
delete detailStatusById[modelId];
|
|
1274
1285
|
delete detailErrorById[modelId];
|
|
1275
|
-
return { ...
|
|
1286
|
+
return { ...s4, detailStatusById, detailErrorById };
|
|
1276
1287
|
});
|
|
1277
1288
|
},
|
|
1278
1289
|
loadClassConfig(modelId, offeringId) {
|
|
@@ -1280,37 +1291,37 @@ function createModelRecords(config) {
|
|
|
1280
1291
|
const status = state2.classConfigStatusByOffering[key];
|
|
1281
1292
|
if (status === "loading" || status === "succeeded") return inFlight.get(`class:${key}`) ?? Promise.resolve();
|
|
1282
1293
|
return once(`class:${key}`, async () => {
|
|
1283
|
-
update((
|
|
1294
|
+
update((s4) => ({ ...s4, classConfigStatusByOffering: { ...s4.classConfigStatusByOffering, [key]: "loading" } }));
|
|
1284
1295
|
try {
|
|
1285
1296
|
const { data, error } = await ai().rpc("resolve_model_config", { p_model_id: modelId, p_offering_id: offeringId });
|
|
1286
1297
|
if (error) throw error;
|
|
1287
1298
|
const cfg = data && typeof data === "object" && !Array.isArray(data) ? data : null;
|
|
1288
|
-
update((
|
|
1289
|
-
...
|
|
1290
|
-
classConfigStatusByOffering: { ...
|
|
1299
|
+
update((s4) => ({
|
|
1300
|
+
...s4,
|
|
1301
|
+
classConfigStatusByOffering: { ...s4.classConfigStatusByOffering, [key]: "succeeded" },
|
|
1291
1302
|
classConfigByOffering: {
|
|
1292
|
-
...
|
|
1303
|
+
...s4.classConfigByOffering,
|
|
1293
1304
|
[key]: { modelId, controls: cfg?.controls ?? null, constraints: cfg?.constraints ?? null }
|
|
1294
1305
|
}
|
|
1295
1306
|
}));
|
|
1296
1307
|
} catch (err) {
|
|
1297
1308
|
errorSink({ code: "model-records-class-config-failed", message: `Model class configuration ${modelId} / ${offeringId}: ${messageOf(err)}` });
|
|
1298
|
-
update((
|
|
1309
|
+
update((s4) => ({ ...s4, classConfigStatusByOffering: { ...s4.classConfigStatusByOffering, [key]: "failed" } }));
|
|
1299
1310
|
}
|
|
1300
1311
|
});
|
|
1301
1312
|
},
|
|
1302
1313
|
loadClasses() {
|
|
1303
1314
|
if (state2.modelClassStatus === "loading" || state2.modelClassStatus === "succeeded") return inFlight.get("classes") ?? Promise.resolve();
|
|
1304
1315
|
return once("classes", async () => {
|
|
1305
|
-
update((
|
|
1316
|
+
update((s4) => ({ ...s4, modelClassStatus: "loading" }));
|
|
1306
1317
|
try {
|
|
1307
1318
|
const { data, error } = await ai().from("model_offering").select("model_id, offering_id, served_via, served_via_endpoint_id, priority");
|
|
1308
1319
|
if (error) throw error;
|
|
1309
1320
|
const index = buildModelClassIndex(data ?? []);
|
|
1310
|
-
update((
|
|
1321
|
+
update((s4) => ({ ...s4, modelClassStatus: "succeeded", modelClassIndex: index }));
|
|
1311
1322
|
} catch (err) {
|
|
1312
1323
|
errorSink({ code: "model-records-classes-failed", message: `Model classes: ${messageOf(err)}` });
|
|
1313
|
-
update((
|
|
1324
|
+
update((s4) => ({ ...s4, modelClassStatus: "failed" }));
|
|
1314
1325
|
}
|
|
1315
1326
|
});
|
|
1316
1327
|
},
|
|
@@ -1325,11 +1336,11 @@ function createModelRecords(config) {
|
|
|
1325
1336
|
if (error) throw error;
|
|
1326
1337
|
const slice = buildModelClassIndex(data ?? []);
|
|
1327
1338
|
classesReadFor.add(modelId);
|
|
1328
|
-
update((
|
|
1329
|
-
if (
|
|
1330
|
-
const base =
|
|
1339
|
+
update((s4) => {
|
|
1340
|
+
if (s4.modelClassStatus === "succeeded") return s4;
|
|
1341
|
+
const base = s4.modelClassIndex ?? { classesByModelId: {}, classOfOffering: {} };
|
|
1331
1342
|
return {
|
|
1332
|
-
...
|
|
1343
|
+
...s4,
|
|
1333
1344
|
modelClassIndex: {
|
|
1334
1345
|
classesByModelId: { ...base.classesByModelId, ...slice.classesByModelId },
|
|
1335
1346
|
classOfOffering: { ...base.classOfOffering, ...slice.classOfOffering }
|
|
@@ -1347,20 +1358,20 @@ function createModelRecords(config) {
|
|
|
1347
1358
|
return inFlight.get(`identity:${modelId}`) ?? Promise.resolve();
|
|
1348
1359
|
}
|
|
1349
1360
|
return once(`identity:${modelId}`, async () => {
|
|
1350
|
-
update((
|
|
1361
|
+
update((s4) => ({ ...s4, identityStatusById: { ...s4.identityStatusById, [modelId]: "loading" } }));
|
|
1351
1362
|
try {
|
|
1352
1363
|
const { data, error } = await ai().from("model_definition").select("id, name, common_name, is_deprecated").eq(catalogKeyColumn(modelId), modelId).maybeSingle();
|
|
1353
1364
|
if (error) throw error;
|
|
1354
1365
|
if (!data) throw new Error(`AI model ${modelId} is not available (ai.model_definition has no such row)`);
|
|
1355
1366
|
const row = data;
|
|
1356
|
-
update((
|
|
1357
|
-
...
|
|
1358
|
-
identityById: { ...
|
|
1359
|
-
identityStatusById: { ...
|
|
1367
|
+
update((s4) => ({
|
|
1368
|
+
...s4,
|
|
1369
|
+
identityById: { ...s4.identityById, [row.id]: row, [modelId]: row },
|
|
1370
|
+
identityStatusById: { ...s4.identityStatusById, [row.id]: "succeeded", [modelId]: "succeeded" }
|
|
1360
1371
|
}));
|
|
1361
1372
|
} catch (err) {
|
|
1362
1373
|
errorSink({ code: "model-records-identity-failed", message: messageOf(err), context: { modelId } });
|
|
1363
|
-
update((
|
|
1374
|
+
update((s4) => ({ ...s4, identityStatusById: { ...s4.identityStatusById, [modelId]: "failed" } }));
|
|
1364
1375
|
}
|
|
1365
1376
|
});
|
|
1366
1377
|
},
|
|
@@ -1379,26 +1390,26 @@ function createModelRecords(config) {
|
|
|
1379
1390
|
}
|
|
1380
1391
|
if (wanted.length > 0) {
|
|
1381
1392
|
const marked = Object.fromEntries(wanted.map((id) => [id, "loading"]));
|
|
1382
|
-
update((
|
|
1393
|
+
update((s4) => ({ ...s4, identityStatusById: { ...s4.identityStatusById, ...marked } }));
|
|
1383
1394
|
const run = (async () => {
|
|
1384
1395
|
try {
|
|
1385
1396
|
const { data, error } = await ai().from("model_definition").select("id, name, common_name, is_deprecated").in("id", wanted);
|
|
1386
1397
|
if (error) throw error;
|
|
1387
1398
|
const rows3 = Array.isArray(data) ? data : [];
|
|
1388
1399
|
const got = new Set(rows3.map((r) => r.id));
|
|
1389
|
-
update((
|
|
1390
|
-
...
|
|
1391
|
-
identityById: { ...
|
|
1400
|
+
update((s4) => ({
|
|
1401
|
+
...s4,
|
|
1402
|
+
identityById: { ...s4.identityById, ...Object.fromEntries(rows3.map((r) => [r.id, r])) },
|
|
1392
1403
|
identityStatusById: {
|
|
1393
|
-
...
|
|
1404
|
+
...s4.identityStatusById,
|
|
1394
1405
|
...Object.fromEntries(wanted.map((id) => [id, got.has(id) ? "succeeded" : "failed"]))
|
|
1395
1406
|
}
|
|
1396
1407
|
}));
|
|
1397
1408
|
} catch (err) {
|
|
1398
1409
|
errorSink({ code: "model-records-identity-failed", message: messageOf(err), context: { modelIds: wanted } });
|
|
1399
|
-
update((
|
|
1400
|
-
...
|
|
1401
|
-
identityStatusById: { ...
|
|
1410
|
+
update((s4) => ({
|
|
1411
|
+
...s4,
|
|
1412
|
+
identityStatusById: { ...s4.identityStatusById, ...Object.fromEntries(wanted.map((id) => [id, "failed"])) }
|
|
1402
1413
|
}));
|
|
1403
1414
|
}
|
|
1404
1415
|
})();
|
|
@@ -1416,11 +1427,11 @@ function createModelRecords(config) {
|
|
|
1416
1427
|
if (!classesReadFor.has(modelId) && state2.modelClassStatus !== "succeeded") {
|
|
1417
1428
|
const slice = buildModelClassIndex(offerings.filter((o) => o.model_id === modelId));
|
|
1418
1429
|
classesReadFor.add(modelId);
|
|
1419
|
-
update((
|
|
1420
|
-
if (
|
|
1421
|
-
const base =
|
|
1430
|
+
update((s4) => {
|
|
1431
|
+
if (s4.modelClassStatus === "succeeded") return s4;
|
|
1432
|
+
const base = s4.modelClassIndex ?? { classesByModelId: {}, classOfOffering: {} };
|
|
1422
1433
|
return {
|
|
1423
|
-
...
|
|
1434
|
+
...s4,
|
|
1424
1435
|
modelClassIndex: {
|
|
1425
1436
|
classesByModelId: { ...base.classesByModelId, ...slice.classesByModelId },
|
|
1426
1437
|
classOfOffering: { ...base.classOfOffering, ...slice.classOfOffering }
|
|
@@ -1439,16 +1450,16 @@ function createModelRecords(config) {
|
|
|
1439
1450
|
controls: row.controls ?? null,
|
|
1440
1451
|
capabilities: requireCanonicalCapabilities(row.capabilities, { modelId: row.id, modelName: row.name })
|
|
1441
1452
|
});
|
|
1442
|
-
update((
|
|
1443
|
-
const existing =
|
|
1444
|
-
const detailErrorById = { ...
|
|
1453
|
+
update((s4) => {
|
|
1454
|
+
const existing = s4.entities[record.id];
|
|
1455
|
+
const detailErrorById = { ...s4.detailErrorById };
|
|
1445
1456
|
delete detailErrorById[modelId];
|
|
1446
1457
|
return withIdLists({
|
|
1447
|
-
...
|
|
1448
|
-
detailStatusById: { ...
|
|
1458
|
+
...s4,
|
|
1459
|
+
detailStatusById: { ...s4.detailStatusById, [modelId]: "succeeded" },
|
|
1449
1460
|
detailErrorById,
|
|
1450
1461
|
entities: {
|
|
1451
|
-
...
|
|
1462
|
+
...s4.entities,
|
|
1452
1463
|
[record.id]: { ...emptyRecord(), ...existing, ...record, maker: record.maker ?? existing?.maker ?? null, _fetchType: "full" }
|
|
1453
1464
|
}
|
|
1454
1465
|
});
|
|
@@ -1458,14 +1469,14 @@ function createModelRecords(config) {
|
|
|
1458
1469
|
}
|
|
1459
1470
|
},
|
|
1460
1471
|
hydrate({ models, fetchType, fetchScope, lastFetched }) {
|
|
1461
|
-
update((
|
|
1462
|
-
const entities = { ...
|
|
1472
|
+
update((s4) => {
|
|
1473
|
+
const entities = { ...s4.entities };
|
|
1463
1474
|
for (const raw of models) {
|
|
1464
1475
|
const existing = entities[raw.id];
|
|
1465
1476
|
if (existing?._fetchType === "full" && fetchType === "options") continue;
|
|
1466
1477
|
entities[raw.id] = { ...emptyRecord(), ...normalizeModel({ ...raw, controls: raw.controls ?? null }), _fetchType: fetchType };
|
|
1467
1478
|
}
|
|
1468
|
-
return withIdLists({ ...
|
|
1479
|
+
return withIdLists({ ...s4, entities, fetchScope, lastFetched, isLoading: false, error: null });
|
|
1469
1480
|
});
|
|
1470
1481
|
}
|
|
1471
1482
|
};
|
|
@@ -1485,28 +1496,28 @@ function memo(fn) {
|
|
|
1485
1496
|
var UNSET = /* @__PURE__ */ Symbol("unset");
|
|
1486
1497
|
var activeKey = null;
|
|
1487
1498
|
var activeOut = [];
|
|
1488
|
-
function selectActiveModels(
|
|
1489
|
-
if (!activeKey || activeKey[0] !==
|
|
1490
|
-
activeKey = [
|
|
1491
|
-
activeOut =
|
|
1499
|
+
function selectActiveModels(s4) {
|
|
1500
|
+
if (!activeKey || activeKey[0] !== s4.entities || activeKey[1] !== s4.activeIds) {
|
|
1501
|
+
activeKey = [s4.entities, s4.activeIds];
|
|
1502
|
+
activeOut = s4.activeIds.map((id) => s4.entities[id]).filter((m) => !!m);
|
|
1492
1503
|
}
|
|
1493
1504
|
return activeOut;
|
|
1494
1505
|
}
|
|
1495
1506
|
var deprecatedKey = null;
|
|
1496
1507
|
var deprecatedOut = [];
|
|
1497
|
-
function selectDeprecatedModels(
|
|
1498
|
-
if (!deprecatedKey || deprecatedKey[0] !==
|
|
1499
|
-
deprecatedKey = [
|
|
1500
|
-
deprecatedOut =
|
|
1508
|
+
function selectDeprecatedModels(s4) {
|
|
1509
|
+
if (!deprecatedKey || deprecatedKey[0] !== s4.entities || deprecatedKey[1] !== s4.deprecatedIds) {
|
|
1510
|
+
deprecatedKey = [s4.entities, s4.deprecatedIds];
|
|
1511
|
+
deprecatedOut = s4.deprecatedIds.map((id) => s4.entities[id]).filter((m) => !!m);
|
|
1501
1512
|
}
|
|
1502
1513
|
return deprecatedOut;
|
|
1503
1514
|
}
|
|
1504
1515
|
var allOf = memo((entities) => Object.values(entities));
|
|
1505
|
-
var selectAllModels = (
|
|
1516
|
+
var selectAllModels = (s4) => allOf(s4.entities);
|
|
1506
1517
|
var optionsOf = memo(
|
|
1507
1518
|
(models) => models.map((m) => ({ value: m.id, label: modelDisplayLabel(m) || m.id, maker: m.maker, costRating: m.cost_rating, speedRating: m.speed_rating }))
|
|
1508
1519
|
);
|
|
1509
|
-
var selectModelOptions = (
|
|
1520
|
+
var selectModelOptions = (s4) => optionsOf(selectActiveModels(s4));
|
|
1510
1521
|
var allOptionsOf = memo(
|
|
1511
1522
|
(entities) => Object.values(entities).map((m) => ({
|
|
1512
1523
|
value: m.id,
|
|
@@ -1517,30 +1528,30 @@ var allOptionsOf = memo(
|
|
|
1517
1528
|
successorId: m.successor_id ?? null
|
|
1518
1529
|
}))
|
|
1519
1530
|
);
|
|
1520
|
-
var selectAllModelOptions = (
|
|
1521
|
-
var selectModelById = (
|
|
1522
|
-
function selectModelLabelById(
|
|
1531
|
+
var selectAllModelOptions = (s4) => allOptionsOf(s4.entities);
|
|
1532
|
+
var selectModelById = (s4, modelId) => modelId ? s4.entities[modelId] : void 0;
|
|
1533
|
+
function selectModelLabelById(s4, modelId) {
|
|
1523
1534
|
if (!modelId) return void 0;
|
|
1524
|
-
const m =
|
|
1525
|
-
const identity =
|
|
1535
|
+
const m = s4.entities[modelId];
|
|
1536
|
+
const identity = s4.identityById[modelId];
|
|
1526
1537
|
return modelDisplayLabel(m) || modelDisplayLabel(identity);
|
|
1527
1538
|
}
|
|
1528
|
-
var selectModelNameById = (
|
|
1529
|
-
var selectModelClassName = (
|
|
1530
|
-
function selectModelLabelWithClass(
|
|
1531
|
-
const label = selectModelLabelById(
|
|
1532
|
-
return label ? withModelClass(label, selectModelClassName(
|
|
1533
|
-
}
|
|
1534
|
-
var selectModelControls = (
|
|
1535
|
-
var selectModelFetchType = (
|
|
1536
|
-
var selectModelFullyLoaded = (
|
|
1537
|
-
var selectModelDetailError = (
|
|
1538
|
-
var selectModelClassConfig = (
|
|
1539
|
-
var selectActiveModelsReady = (
|
|
1540
|
-
var selectDeprecatedModelsReady = (
|
|
1541
|
-
var selectAllModelsReady = (
|
|
1542
|
-
var selectModelIdentityById = (
|
|
1543
|
-
var selectModelIdentityLookupStatus = (
|
|
1539
|
+
var selectModelNameById = (s4, modelId) => modelId ? s4.entities[modelId]?.name : void 0;
|
|
1540
|
+
var selectModelClassName = (s4, modelId, offeringId) => modelClassName(s4.modelClassIndex, modelId, offeringId);
|
|
1541
|
+
function selectModelLabelWithClass(s4, modelId, offeringId) {
|
|
1542
|
+
const label = selectModelLabelById(s4, modelId);
|
|
1543
|
+
return label ? withModelClass(label, selectModelClassName(s4, modelId, offeringId)) : void 0;
|
|
1544
|
+
}
|
|
1545
|
+
var selectModelControls = (s4, modelId) => s4.entities[modelId]?.controls;
|
|
1546
|
+
var selectModelFetchType = (s4, modelId) => s4.entities[modelId]?._fetchType;
|
|
1547
|
+
var selectModelFullyLoaded = (s4, modelId) => !!modelId && s4.entities[modelId]?._fetchType === "full";
|
|
1548
|
+
var selectModelDetailError = (s4, modelId) => modelId && s4.detailErrorById[modelId] || null;
|
|
1549
|
+
var selectModelClassConfig = (s4, modelId, offeringId) => modelId && offeringId ? s4.classConfigByOffering[classConfigKey(modelId, offeringId)] : void 0;
|
|
1550
|
+
var selectActiveModelsReady = (s4) => (s4.fetchScope === "active" || s4.fetchScope === "all") && s4.activeIds.length > 0;
|
|
1551
|
+
var selectDeprecatedModelsReady = (s4) => (s4.fetchScope === "deprecated" || s4.fetchScope === "all") && s4.deprecatedIds.length > 0;
|
|
1552
|
+
var selectAllModelsReady = (s4) => s4.fetchScope === "all";
|
|
1553
|
+
var selectModelIdentityById = (s4, modelId) => s4.identityById[modelId];
|
|
1554
|
+
var selectModelIdentityLookupStatus = (s4, modelId) => s4.identityStatusById[modelId] ?? "idle";
|
|
1544
1555
|
async function readModelIdentities(client, modelIds) {
|
|
1545
1556
|
const ids = [...new Set(modelIds.filter(Boolean))];
|
|
1546
1557
|
if (ids.length === 0) return [];
|
|
@@ -1840,9 +1851,9 @@ function createModelCatalog(config) {
|
|
|
1840
1851
|
};
|
|
1841
1852
|
},
|
|
1842
1853
|
load(variant) {
|
|
1843
|
-
const
|
|
1844
|
-
if (
|
|
1845
|
-
if (
|
|
1854
|
+
const s4 = state2[variant];
|
|
1855
|
+
if (s4.status === "idle" || s4.status === "error") return run(variant);
|
|
1856
|
+
if (s4.status === "ready" && s4.loadedAt !== null && Date.now() - s4.loadedAt > staleAfterMs) return run(variant);
|
|
1846
1857
|
return inFlight[variant] ?? Promise.resolve();
|
|
1847
1858
|
},
|
|
1848
1859
|
reload: (variant) => run(variant)
|
|
@@ -2030,9 +2041,9 @@ function placement(v, runtimes, device) {
|
|
|
2030
2041
|
}
|
|
2031
2042
|
return "not_compatible";
|
|
2032
2043
|
}
|
|
2033
|
-
function formatMemoryBytes(
|
|
2034
|
-
const g =
|
|
2035
|
-
if (g < 1) return `${Math.max(1, Math.round(
|
|
2044
|
+
function formatMemoryBytes(n3) {
|
|
2045
|
+
const g = n3 / 2 ** 30;
|
|
2046
|
+
if (g < 1) return `${Math.max(1, Math.round(n3 / 2 ** 20))} MB`;
|
|
2036
2047
|
return `${g < 10 ? Math.round(g * 10) / 10 : Math.round(g)} GB`;
|
|
2037
2048
|
}
|
|
2038
2049
|
function describe(c, decided, runnerUp, device) {
|
|
@@ -2359,41 +2370,41 @@ var Cursor = class {
|
|
|
2359
2370
|
this.read = read;
|
|
2360
2371
|
this.size = size;
|
|
2361
2372
|
}
|
|
2362
|
-
async need(
|
|
2363
|
-
if (this.pos +
|
|
2364
|
-
if (this.pos +
|
|
2365
|
-
if (this.pos >= this.base && this.pos +
|
|
2366
|
-
const len = Math.min(Math.max(
|
|
2373
|
+
async need(n3) {
|
|
2374
|
+
if (this.pos + n3 > this.size) throw new GgufError("the file ends inside its header");
|
|
2375
|
+
if (this.pos + n3 > MAX_HEADER) throw new GgufError("its header is larger than any real model's");
|
|
2376
|
+
if (this.pos >= this.base && this.pos + n3 <= this.base + this.buf.length) return;
|
|
2377
|
+
const len = Math.min(Math.max(n3, CHUNK), this.size - this.pos);
|
|
2367
2378
|
this.buf = await this.read(this.pos, len);
|
|
2368
2379
|
this.base = this.pos;
|
|
2369
|
-
if (this.buf.length <
|
|
2380
|
+
if (this.buf.length < n3) throw new GgufError("the file ends inside its header");
|
|
2370
2381
|
}
|
|
2371
|
-
async view(
|
|
2372
|
-
await this.need(
|
|
2373
|
-
const v = new DataView(this.buf.buffer, this.buf.byteOffset + (this.pos - this.base),
|
|
2374
|
-
this.pos +=
|
|
2382
|
+
async view(n3) {
|
|
2383
|
+
await this.need(n3);
|
|
2384
|
+
const v = new DataView(this.buf.buffer, this.buf.byteOffset + (this.pos - this.base), n3);
|
|
2385
|
+
this.pos += n3;
|
|
2375
2386
|
return v;
|
|
2376
2387
|
}
|
|
2377
2388
|
async u32() {
|
|
2378
2389
|
return (await this.view(4)).getUint32(0, true);
|
|
2379
2390
|
}
|
|
2380
2391
|
async u64() {
|
|
2381
|
-
const
|
|
2382
|
-
if (
|
|
2383
|
-
return Number(
|
|
2392
|
+
const n3 = (await this.view(8)).getBigUint64(0, true);
|
|
2393
|
+
if (n3 > BigInt(Number.MAX_SAFE_INTEGER)) throw new GgufError("a size in its header is out of range");
|
|
2394
|
+
return Number(n3);
|
|
2384
2395
|
}
|
|
2385
2396
|
async str() {
|
|
2386
2397
|
const len = await this.u64();
|
|
2387
2398
|
if (len > MAX_STRING) throw new GgufError("a text value in its header is out of range");
|
|
2388
2399
|
await this.need(len);
|
|
2389
2400
|
const at = this.pos - this.base;
|
|
2390
|
-
const
|
|
2401
|
+
const s4 = new TextDecoder().decode(this.buf.subarray(at, at + len));
|
|
2391
2402
|
this.pos += len;
|
|
2392
|
-
return
|
|
2403
|
+
return s4;
|
|
2393
2404
|
}
|
|
2394
|
-
async skip(
|
|
2395
|
-
if (this.pos +
|
|
2396
|
-
this.pos +=
|
|
2405
|
+
async skip(n3) {
|
|
2406
|
+
if (this.pos + n3 > this.size) throw new GgufError("the file ends inside its header");
|
|
2407
|
+
this.pos += n3;
|
|
2397
2408
|
}
|
|
2398
2409
|
async scalar(type) {
|
|
2399
2410
|
switch (type) {
|
|
@@ -2445,11 +2456,11 @@ async function readGgufHeader(read, size) {
|
|
|
2445
2456
|
const type = await c.u32();
|
|
2446
2457
|
if (type === 9) {
|
|
2447
2458
|
const elem = await c.u32();
|
|
2448
|
-
const
|
|
2449
|
-
if (
|
|
2450
|
-
arrays[key] =
|
|
2451
|
-
if (elem === 8) for (let j = 0; j <
|
|
2452
|
-
else if (SCALAR_BYTES[elem] !== void 0) await c.skip(
|
|
2459
|
+
const n3 = await c.u64();
|
|
2460
|
+
if (n3 > 64 * MAX_COUNT) throw new GgufError("an array in its header is out of range");
|
|
2461
|
+
arrays[key] = n3;
|
|
2462
|
+
if (elem === 8) for (let j = 0; j < n3; j++) await c.str();
|
|
2463
|
+
else if (SCALAR_BYTES[elem] !== void 0) await c.skip(n3 * SCALAR_BYTES[elem]);
|
|
2453
2464
|
else throw new GgufError(`its header has an array of unknown type ${elem}`);
|
|
2454
2465
|
} else kv[key] = await c.scalar(type);
|
|
2455
2466
|
}
|
|
@@ -2479,20 +2490,20 @@ async function readGgufHeader(read, size) {
|
|
|
2479
2490
|
return { version, kv, array_lengths: arrays, tensors, data_offset: dataOffset, data_end: end };
|
|
2480
2491
|
}
|
|
2481
2492
|
function ggufFacts(h) {
|
|
2482
|
-
const
|
|
2483
|
-
const
|
|
2484
|
-
const arch =
|
|
2485
|
-
const ft =
|
|
2493
|
+
const s4 = (k) => typeof h.kv[k] === "string" ? h.kv[k] : null;
|
|
2494
|
+
const n3 = (k) => typeof h.kv[k] === "number" ? h.kv[k] : null;
|
|
2495
|
+
const arch = s4("general.architecture");
|
|
2496
|
+
const ft = n3("general.file_type");
|
|
2486
2497
|
const params = h.tensors.reduce((t, x) => t + x.dims.reduce((a, b) => a * b, 1), 0);
|
|
2487
2498
|
return {
|
|
2488
2499
|
architecture: arch,
|
|
2489
|
-
name:
|
|
2500
|
+
name: s4("general.name"),
|
|
2490
2501
|
quant: ft !== null ? FILE_TYPE_QUANT[ft] ?? `FTYPE_${ft}` : null,
|
|
2491
|
-
context_length: arch ?
|
|
2502
|
+
context_length: arch ? n3(`${arch}.context_length`) : null,
|
|
2492
2503
|
parameter_count: params > 0 ? params : null,
|
|
2493
|
-
split_count:
|
|
2494
|
-
split_index:
|
|
2495
|
-
type:
|
|
2504
|
+
split_count: n3("split.count") ?? 1,
|
|
2505
|
+
split_index: n3("split.no") ?? 0,
|
|
2506
|
+
type: s4("general.type"),
|
|
2496
2507
|
has_tokenizer: typeof h.kv["tokenizer.ggml.model"] === "string"
|
|
2497
2508
|
};
|
|
2498
2509
|
}
|
|
@@ -2533,16 +2544,16 @@ var isHelperGguf = (name) => HELPER_RE.test(name);
|
|
|
2533
2544
|
var isProjectorName = (name) => name.toLowerCase().includes("mmproj");
|
|
2534
2545
|
var quantFromName = (name) => QUANT_RE.exec(name.split("/").pop() ?? name)?.[1]?.toUpperCase() ?? null;
|
|
2535
2546
|
function splitSetProblems(names) {
|
|
2536
|
-
const shards = names.map(shardName).filter((
|
|
2547
|
+
const shards = names.map(shardName).filter((s4) => s4 !== null);
|
|
2537
2548
|
if (!shards.length) return [];
|
|
2538
|
-
const counts = [...new Set(shards.map((
|
|
2549
|
+
const counts = [...new Set(shards.map((s4) => s4.count))];
|
|
2539
2550
|
if (counts.length !== 1) return [`its parts disagree on how many parts there are (${counts.sort((a, b) => a - b).join(", ")})`];
|
|
2540
|
-
const
|
|
2541
|
-
const have = shards.map((
|
|
2542
|
-
const missing = Array.from({ length:
|
|
2551
|
+
const n3 = counts[0];
|
|
2552
|
+
const have = shards.map((s4) => s4.index);
|
|
2553
|
+
const missing = Array.from({ length: n3 }, (_, i) => i + 1).filter((i) => !have.includes(i));
|
|
2543
2554
|
const dupes = [...new Set(have.filter((i, k) => have.indexOf(i) !== k))].sort((a, b) => a - b);
|
|
2544
2555
|
const out = [];
|
|
2545
|
-
if (missing.length) out.push(`part${missing.length === 1 ? "" : "s"} ${missing.map((i) => `${i} of ${
|
|
2556
|
+
if (missing.length) out.push(`part${missing.length === 1 ? "" : "s"} ${missing.map((i) => `${i} of ${n3}`).join(", ")} ${missing.length === 1 ? "is" : "are"} missing`);
|
|
2546
2557
|
if (dupes.length) out.push(`part${dupes.length === 1 ? "" : "s"} ${dupes.join(", ")} appear${dupes.length === 1 ? "s" : ""} twice`);
|
|
2547
2558
|
return out;
|
|
2548
2559
|
}
|
|
@@ -2579,8 +2590,8 @@ async function validateModelSet(primary, siblings, env) {
|
|
|
2579
2590
|
let parts = [primary];
|
|
2580
2591
|
if (shard) {
|
|
2581
2592
|
if (shard.index !== 1) return { ok: false, reason: `This is part ${shard.index} of ${shard.count} of a split model; add its first part, ${shardFileName(shard.stem, 1, shard.count)}.` };
|
|
2582
|
-
const same = [primary, ...siblings.filter((
|
|
2583
|
-
const problems = splitSetProblems(same.map((
|
|
2593
|
+
const same = [primary, ...siblings.filter((s4) => s4 !== primary && shardName(s4.name)?.stem === shard.stem)];
|
|
2594
|
+
const problems = splitSetProblems(same.map((s4) => s4.name));
|
|
2584
2595
|
if (problems.length) return { ok: false, reason: `It is a model in ${shard.count} parts and ${problems.join("; ")} (every part has to be in the same folder).` };
|
|
2585
2596
|
parts = same.sort((a, b) => (shardName(a.name)?.index ?? 0) - (shardName(b.name)?.index ?? 0));
|
|
2586
2597
|
}
|
|
@@ -2613,7 +2624,7 @@ async function validateModelSet(primary, siblings, env) {
|
|
|
2613
2624
|
if (end !== null && p.size < end) return { ok: false, reason: `${parts.length > 1 ? `Part ${i + 1} of ${parts.length} is incomplete` : "The file is incomplete"}: it is ${formatMemoryBytes(p.size)} and its header says ${formatMemoryBytes(end)} (a download that stopped early?).` };
|
|
2614
2625
|
}
|
|
2615
2626
|
const quant = f.quant ?? quantFromName(primary.name) ?? "unknown";
|
|
2616
|
-
const projectorFiles = siblings.filter((
|
|
2627
|
+
const projectorFiles = siblings.filter((s4) => s4 !== primary && extOf(s4.name) === ".gguf" && isProjectorName(s4.name));
|
|
2617
2628
|
const pickedName = pickProjector(quant === "unknown" ? null : quant, projectorFiles.map((p) => p.name));
|
|
2618
2629
|
let projector = null;
|
|
2619
2630
|
const picked = projectorFiles.find((p) => p.name === pickedName);
|
|
@@ -2735,6 +2746,79 @@ function curatedList(models, { offeredOnly }) {
|
|
|
2735
2746
|
|
|
2736
2747
|
// models/catalog-reader.ts
|
|
2737
2748
|
var import_sha256 = require("@ai-matrx/kit/sha256");
|
|
2749
|
+
|
|
2750
|
+
// models/device-models.ts
|
|
2751
|
+
var DEVICE_SERVED_VIA = "device";
|
|
2752
|
+
var DEVICE_SERVED_SETTINGS_KEY = "served_via";
|
|
2753
|
+
var DEVICE_RULE_TWIN_FIXTURE = "apps/shared/matrx-agents/models/__tests__/device-models.test.ts";
|
|
2754
|
+
var DEVICE_FILE_USABLE_STATES = Object.freeze(["present"]);
|
|
2755
|
+
var DEVICE_OWN_FILE_USABLE_STATES = Object.freeze(["present", "unmatched"]);
|
|
2756
|
+
var s2 = (v) => typeof v === "string" ? v : "";
|
|
2757
|
+
var sn2 = (v) => typeof v === "string" ? v : null;
|
|
2758
|
+
var n = (v) => typeof v === "number" && Number.isFinite(v) ? v : typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v)) ? Number(v) : null;
|
|
2759
|
+
function toDeviceModelFile(r) {
|
|
2760
|
+
return {
|
|
2761
|
+
id: s2(r.id),
|
|
2762
|
+
app_instance_id: s2(r.app_instance_id),
|
|
2763
|
+
root_id: s2(r.root_id),
|
|
2764
|
+
rel_path: sn2(r.rel_path),
|
|
2765
|
+
size_bytes: n(r.size_bytes),
|
|
2766
|
+
sha256: sn2(r.sha256),
|
|
2767
|
+
artifact_id: sn2(r.artifact_id),
|
|
2768
|
+
state: s2(r.state),
|
|
2769
|
+
organization_id: sn2(r.organization_id)
|
|
2770
|
+
};
|
|
2771
|
+
}
|
|
2772
|
+
async function readDeviceModelFiles(client, opts = {}) {
|
|
2773
|
+
const db = client;
|
|
2774
|
+
let q = db.schema("ai").from("device_model_file").select("*").is("deleted_at", null).in("state", opts.states ?? DEVICE_FILE_USABLE_STATES);
|
|
2775
|
+
if (opts.appInstanceIds) q = q.in("app_instance_id", opts.appInstanceIds);
|
|
2776
|
+
const { data, error } = await q;
|
|
2777
|
+
if (error) throw new Error(`ai.device_model_file: ${error.message}`);
|
|
2778
|
+
return (data ?? []).filter((r) => r.deleted_at == null).map(toDeviceModelFile);
|
|
2779
|
+
}
|
|
2780
|
+
function requiredArtifactIds(v) {
|
|
2781
|
+
const core = v.artifacts.filter((a) => a.required_for.includes("core"));
|
|
2782
|
+
return (core.length ? core : v.artifacts).map((a) => a.id);
|
|
2783
|
+
}
|
|
2784
|
+
function availableOnDevices(variants, files) {
|
|
2785
|
+
const usable = files.filter((f) => f.artifact_id && DEVICE_FILE_USABLE_STATES.includes(f.state));
|
|
2786
|
+
const out = /* @__PURE__ */ new Map();
|
|
2787
|
+
for (const v of variants) {
|
|
2788
|
+
const order = new Map(v.artifacts.map((a, i) => [a.id, i]));
|
|
2789
|
+
const required = requiredArtifactIds(v);
|
|
2790
|
+
const byDevice = /* @__PURE__ */ new Map();
|
|
2791
|
+
for (const f of usable)
|
|
2792
|
+
if (order.has(f.artifact_id)) {
|
|
2793
|
+
const list2 = byDevice.get(f.app_instance_id) ?? [];
|
|
2794
|
+
list2.push(f);
|
|
2795
|
+
byDevice.set(f.app_instance_id, list2);
|
|
2796
|
+
}
|
|
2797
|
+
const list = [];
|
|
2798
|
+
for (const device of [...byDevice.keys()].sort()) {
|
|
2799
|
+
const held = byDevice.get(device);
|
|
2800
|
+
const have = new Set(held.map((f) => f.artifact_id));
|
|
2801
|
+
if (!required.length || !required.every((id) => have.has(id))) continue;
|
|
2802
|
+
list.push({
|
|
2803
|
+
app_instance_id: device,
|
|
2804
|
+
variant_id: v.id,
|
|
2805
|
+
device_model_file_ids: held.slice().sort((a, b) => order.get(a.artifact_id) - order.get(b.artifact_id) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)).map((f) => f.id)
|
|
2806
|
+
});
|
|
2807
|
+
}
|
|
2808
|
+
out.set(v.id, list);
|
|
2809
|
+
}
|
|
2810
|
+
return out;
|
|
2811
|
+
}
|
|
2812
|
+
var DeviceModelUnavailableError = class extends Error {
|
|
2813
|
+
code;
|
|
2814
|
+
constructor(code, message) {
|
|
2815
|
+
super(message);
|
|
2816
|
+
this.name = "DeviceModelUnavailableError";
|
|
2817
|
+
this.code = code;
|
|
2818
|
+
}
|
|
2819
|
+
};
|
|
2820
|
+
|
|
2821
|
+
// models/catalog-reader.ts
|
|
2738
2822
|
var MODEL_CATALOG_KINDS = /* @__PURE__ */ new Set([
|
|
2739
2823
|
"llm_model",
|
|
2740
2824
|
"whisper_model",
|
|
@@ -2772,14 +2856,17 @@ function catalogFingerprint(catalog) {
|
|
|
2772
2856
|
const { catalog_fingerprint: _omit, ...rest } = catalog;
|
|
2773
2857
|
return (0, import_sha256.sha256Hex)(canonicalJson(rest));
|
|
2774
2858
|
}
|
|
2775
|
-
var
|
|
2776
|
-
var
|
|
2777
|
-
var
|
|
2859
|
+
var s3 = (v) => typeof v === "string" ? v : "";
|
|
2860
|
+
var sn3 = (v) => typeof v === "string" ? v : null;
|
|
2861
|
+
var n2 = (v) => typeof v === "number" && Number.isFinite(v) ? v : typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v)) ? Number(v) : null;
|
|
2778
2862
|
var live2 = (r) => r.deleted_at == null;
|
|
2779
2863
|
var byCodepoint2 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
2780
|
-
var byId = (rows3) => new Map(rows3.map((r) => [
|
|
2864
|
+
var byId = (rows3) => new Map(rows3.map((r) => [s3(r.id), r]));
|
|
2781
2865
|
var strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
2782
2866
|
async function readModelCatalog(client, opts = {}) {
|
|
2867
|
+
return readCatalogWith(client, opts, null);
|
|
2868
|
+
}
|
|
2869
|
+
async function readCatalogWith(client, opts, preread) {
|
|
2783
2870
|
const db = client;
|
|
2784
2871
|
const offeredOnly = opts.offeredOnly ?? true;
|
|
2785
2872
|
const ai = db.schema("ai");
|
|
@@ -2797,20 +2884,29 @@ async function readModelCatalog(client, opts = {}) {
|
|
|
2797
2884
|
]);
|
|
2798
2885
|
const curated = curatedList(shapeCuratedModels(raw), { offeredOnly });
|
|
2799
2886
|
const listed = new Set(curated.map((m) => m.id));
|
|
2800
|
-
const variants = raw.variants.filter((v) => listed.has(
|
|
2887
|
+
const variants = raw.variants.filter((v) => listed.has(s3(v.model_id)) && (offeredOnly ? s3(v.review_state) === "offered" : isListedVariant(v)));
|
|
2801
2888
|
const entryIds = [...new Set(curated.flatMap((m) => m.variants.flatMap((v) => v.catalog_entry_ids)))].sort(byCodepoint2);
|
|
2802
2889
|
const warnings = [];
|
|
2803
|
-
const
|
|
2804
|
-
|
|
2890
|
+
const devices = opts.devices ? opts.devices === true ? {} : opts.devices : null;
|
|
2891
|
+
const [artifacts, entries, deviceFiles] = await Promise.all([
|
|
2892
|
+
variants.length ? all("model_artifact", (q) => q.in("variant_id", variants.map((v) => s3(v.id)))) : Promise.resolve([]),
|
|
2805
2893
|
entryIds.length ? Promise.resolve(db.schema("public").from("catalog_entries").select("id,kind").in("id", entryIds)).then(({ data, error }) => {
|
|
2806
2894
|
if (error) {
|
|
2807
2895
|
warnings.push(`public.catalog_entries: ${error.message} (models placed by what they do)`);
|
|
2808
2896
|
return [];
|
|
2809
2897
|
}
|
|
2810
2898
|
return data ?? [];
|
|
2811
|
-
}) : Promise.resolve([])
|
|
2899
|
+
}) : Promise.resolve([]),
|
|
2900
|
+
devices ? preread ? Promise.resolve(preread) : readDeviceModelFiles(client, devices.appInstanceIds ? { appInstanceIds: devices.appInstanceIds } : {}) : Promise.resolve(null)
|
|
2812
2901
|
]);
|
|
2813
|
-
const
|
|
2902
|
+
const placement2 = deviceFiles ? availableOnDevices(
|
|
2903
|
+
variants.map((v) => ({
|
|
2904
|
+
id: s3(v.id),
|
|
2905
|
+
artifacts: artifacts.filter((a) => s3(a.variant_id) === s3(v.id)).map((a) => ({ id: s3(a.id), required_for: strings(a.required_for) }))
|
|
2906
|
+
})),
|
|
2907
|
+
deviceFiles
|
|
2908
|
+
) : null;
|
|
2909
|
+
const kindOfEntry = new Map(entries.map((e) => [s3(e.id), s3(e.kind)]));
|
|
2814
2910
|
const stamps = [];
|
|
2815
2911
|
const stamp = (r) => {
|
|
2816
2912
|
if (r && typeof r.updated_at === "string") stamps.push(r.updated_at);
|
|
@@ -2818,84 +2914,87 @@ async function readModelCatalog(client, opts = {}) {
|
|
|
2818
2914
|
const modelById = byId(raw.models);
|
|
2819
2915
|
const providerById = byId(raw.providers);
|
|
2820
2916
|
const upstreamById = byId(upstreams);
|
|
2821
|
-
const presentationOf = new Map(raw.presentations.map((p) => [
|
|
2917
|
+
const presentationOf = new Map(raw.presentations.map((p) => [s3(p.model_id), p]));
|
|
2822
2918
|
const models = curated.flatMap((c) => {
|
|
2823
2919
|
const m = modelById.get(c.id);
|
|
2824
2920
|
if (!m) return [];
|
|
2825
2921
|
stamp(m);
|
|
2826
2922
|
const p = presentationOf.get(c.id);
|
|
2827
2923
|
stamp(p);
|
|
2828
|
-
const maker = providerById.get(
|
|
2924
|
+
const maker = providerById.get(s3(m.provider_id));
|
|
2829
2925
|
return [
|
|
2830
2926
|
{
|
|
2831
|
-
id:
|
|
2832
|
-
name:
|
|
2833
|
-
desktop_id:
|
|
2834
|
-
common_name:
|
|
2835
|
-
maker:
|
|
2836
|
-
maker_logo_file_id:
|
|
2837
|
-
description:
|
|
2927
|
+
id: s3(m.id),
|
|
2928
|
+
name: s3(m.name),
|
|
2929
|
+
desktop_id: sn3(m.metadata?.desktop_id),
|
|
2930
|
+
common_name: sn3(m.common_name),
|
|
2931
|
+
maker: sn3(maker?.name),
|
|
2932
|
+
maker_logo_file_id: sn3(maker?.logo_file_id),
|
|
2933
|
+
description: sn3(m.description),
|
|
2838
2934
|
capabilities: m.capabilities ?? {},
|
|
2839
2935
|
kind: catalogKindOf(c, kindOfEntry),
|
|
2840
2936
|
offered: c.offered,
|
|
2841
2937
|
updated_at: c.updated_at,
|
|
2842
2938
|
presentation: p ? {
|
|
2843
|
-
summary:
|
|
2844
|
-
symbol:
|
|
2845
|
-
color:
|
|
2846
|
-
shelf:
|
|
2847
|
-
shelf_title:
|
|
2848
|
-
shelf_subtitle:
|
|
2849
|
-
display_order:
|
|
2850
|
-
page:
|
|
2851
|
-
params_label:
|
|
2939
|
+
summary: sn3(p.summary),
|
|
2940
|
+
symbol: sn3(p.symbol),
|
|
2941
|
+
color: sn3(p.color),
|
|
2942
|
+
shelf: sn3(p.shelf),
|
|
2943
|
+
shelf_title: sn3(p.shelf_title),
|
|
2944
|
+
shelf_subtitle: sn3(p.shelf_subtitle),
|
|
2945
|
+
display_order: n2(p.display_order),
|
|
2946
|
+
page: sn3(p.page),
|
|
2947
|
+
params_label: sn3(p.params_label),
|
|
2852
2948
|
tags: strings(p.tags),
|
|
2853
2949
|
links: Array.isArray(p.links) ? p.links : [],
|
|
2854
|
-
thumbnail_file_id:
|
|
2855
|
-
hero_file_id:
|
|
2950
|
+
thumbnail_file_id: sn3(p.thumbnail_file_id),
|
|
2951
|
+
hero_file_id: sn3(p.hero_file_id),
|
|
2856
2952
|
gallery_file_ids: strings(p.gallery_file_ids).filter((f) => f.length > 0),
|
|
2857
2953
|
facts: p.facts && typeof p.facts === "object" && !Array.isArray(p.facts) ? p.facts : {}
|
|
2858
2954
|
} : null,
|
|
2859
|
-
variants: variants.filter((v) =>
|
|
2955
|
+
variants: variants.filter((v) => s3(v.model_id) === c.id).sort((a, b) => byCodepoint2(s3(a.key), s3(b.key))).map((v) => {
|
|
2860
2956
|
stamp(v);
|
|
2861
|
-
const up = upstreamById.get(
|
|
2957
|
+
const up = upstreamById.get(s3(v.upstream_id));
|
|
2862
2958
|
stamp(up);
|
|
2863
|
-
const arts = artifacts.filter((a) =>
|
|
2959
|
+
const arts = artifacts.filter((a) => s3(a.variant_id) === s3(v.id)).sort((a, b) => (n2(a.shard_index) ?? 0) - (n2(b.shard_index) ?? 0) || byCodepoint2(s3(a.path_in_repo), s3(b.path_in_repo)));
|
|
2864
2960
|
arts.forEach(stamp);
|
|
2865
2961
|
return {
|
|
2866
|
-
id:
|
|
2867
|
-
key:
|
|
2868
|
-
catalog_key: `${
|
|
2869
|
-
review_state:
|
|
2962
|
+
id: s3(v.id),
|
|
2963
|
+
key: s3(v.key),
|
|
2964
|
+
catalog_key: `${s3(m.name)}/${s3(v.key)}`,
|
|
2965
|
+
review_state: s3(v.review_state),
|
|
2870
2966
|
catalog_entry_ids: strings(v.metadata?.catalog_entry_ids),
|
|
2871
2967
|
is_default: v.is_default === true,
|
|
2872
|
-
format:
|
|
2873
|
-
quant:
|
|
2874
|
-
architecture:
|
|
2875
|
-
chat_template_kind:
|
|
2968
|
+
format: s3(v.format),
|
|
2969
|
+
quant: s3(v.quant),
|
|
2970
|
+
architecture: s3(v.architecture),
|
|
2971
|
+
chat_template_kind: s3(v.chat_template_kind),
|
|
2876
2972
|
os_arch: strings(v.os_arch),
|
|
2877
|
-
selection_rank:
|
|
2973
|
+
selection_rank: n2(v.selection_rank) ?? Number.MAX_SAFE_INTEGER,
|
|
2878
2974
|
// internal-bound: a row with no rank sorts last (the column is NOT NULL; this only orders a malformed row)
|
|
2879
|
-
license:
|
|
2880
|
-
gated:
|
|
2881
|
-
total_bytes:
|
|
2882
|
-
memory_bytes_min:
|
|
2883
|
-
memory_bytes_recommended:
|
|
2975
|
+
license: sn3(v.license_id),
|
|
2976
|
+
gated: s3(v.gated),
|
|
2977
|
+
total_bytes: n2(v.total_bytes),
|
|
2978
|
+
memory_bytes_min: n2(v.memory_bytes_min),
|
|
2979
|
+
memory_bytes_recommended: n2(v.memory_bytes_recommended),
|
|
2884
2980
|
tested_on_current_runtime: v.tested_on_current_runtime === true,
|
|
2885
|
-
min_app_version:
|
|
2886
|
-
upstream: up ? { kind:
|
|
2981
|
+
min_app_version: sn3(v.min_app_version),
|
|
2982
|
+
upstream: up ? { kind: s3(up.upstream_kind), repo: sn3(up.repo), revision: sn3(up.revision) } : null,
|
|
2887
2983
|
artifacts: arts.map((a) => ({
|
|
2888
|
-
role:
|
|
2889
|
-
path:
|
|
2890
|
-
url:
|
|
2891
|
-
size_bytes:
|
|
2892
|
-
sha256:
|
|
2984
|
+
role: s3(a.role),
|
|
2985
|
+
path: s3(a.path_in_repo),
|
|
2986
|
+
url: s3(a.url),
|
|
2987
|
+
size_bytes: n2(a.size_bytes) ?? 0,
|
|
2988
|
+
sha256: s3(a.sha256),
|
|
2893
2989
|
required_for: strings(a.required_for).sort(),
|
|
2894
|
-
shard_index:
|
|
2895
|
-
shard_count:
|
|
2990
|
+
shard_index: n2(a.shard_index),
|
|
2991
|
+
shard_count: n2(a.shard_count)
|
|
2896
2992
|
}))
|
|
2897
2993
|
};
|
|
2898
|
-
})
|
|
2994
|
+
}),
|
|
2995
|
+
...placement2 ? {
|
|
2996
|
+
available_on: variants.filter((v) => s3(v.model_id) === c.id).sort((a, b) => byCodepoint2(s3(a.key), s3(b.key))).flatMap((v) => placement2.get(s3(v.id)) ?? [])
|
|
2997
|
+
} : {}
|
|
2899
2998
|
}
|
|
2900
2999
|
];
|
|
2901
3000
|
});
|
|
@@ -2903,36 +3002,36 @@ async function readModelCatalog(client, opts = {}) {
|
|
|
2903
3002
|
$schema: "matrx.model-catalog.snapshot/v1",
|
|
2904
3003
|
catalog_updated_at: "",
|
|
2905
3004
|
models,
|
|
2906
|
-
runtimes: runtimes.sort((a, b) => byCodepoint2(
|
|
3005
|
+
runtimes: runtimes.sort((a, b) => byCodepoint2(s3(a.key), s3(b.key))).map((rt) => {
|
|
2907
3006
|
stamp(rt);
|
|
2908
3007
|
return {
|
|
2909
|
-
key:
|
|
2910
|
-
name:
|
|
2911
|
-
upstream_repo:
|
|
2912
|
-
builds: builds.filter((b) =>
|
|
3008
|
+
key: s3(rt.key),
|
|
3009
|
+
name: s3(rt.name),
|
|
3010
|
+
upstream_repo: sn3(rt.upstream_repo),
|
|
3011
|
+
builds: builds.filter((b) => s3(b.runtime_id) === s3(rt.id)).map((b) => {
|
|
2913
3012
|
stamp(b);
|
|
2914
3013
|
const known = b.metadata?.known_architectures;
|
|
2915
3014
|
return {
|
|
2916
|
-
id:
|
|
2917
|
-
version:
|
|
2918
|
-
os:
|
|
2919
|
-
arch:
|
|
2920
|
-
accelerator:
|
|
2921
|
-
build_kind:
|
|
2922
|
-
url:
|
|
2923
|
-
size_bytes:
|
|
2924
|
-
sha256:
|
|
3015
|
+
id: s3(b.id),
|
|
3016
|
+
version: s3(b.runtime_version),
|
|
3017
|
+
os: s3(b.os),
|
|
3018
|
+
arch: s3(b.arch),
|
|
3019
|
+
accelerator: s3(b.accelerator),
|
|
3020
|
+
build_kind: s3(b.build_kind),
|
|
3021
|
+
url: sn3(b.url),
|
|
3022
|
+
size_bytes: n2(b.size_bytes),
|
|
3023
|
+
sha256: sn3(b.sha256),
|
|
2925
3024
|
is_active: b.is_active === true,
|
|
2926
|
-
min_app_version:
|
|
3025
|
+
min_app_version: sn3(b.min_app_version),
|
|
2927
3026
|
// The architectures this pinned llama-server build loads (runtime_build.metadata): the own-model judge reads it.
|
|
2928
3027
|
known_architectures: Array.isArray(known) ? known.map(String).sort() : null,
|
|
2929
|
-
compat: compat.filter((c) =>
|
|
2930
|
-
hardware_class:
|
|
2931
|
-
format:
|
|
2932
|
-
quant:
|
|
2933
|
-
architecture:
|
|
2934
|
-
chat_template_kind:
|
|
2935
|
-
status:
|
|
3028
|
+
compat: compat.filter((c) => s3(c.runtime_build_id) === s3(b.id)).map((c) => ({
|
|
3029
|
+
hardware_class: s3(c.hardware_class),
|
|
3030
|
+
format: s3(c.format),
|
|
3031
|
+
quant: s3(c.quant),
|
|
3032
|
+
architecture: s3(c.architecture),
|
|
3033
|
+
chat_template_kind: s3(c.chat_template_kind),
|
|
3034
|
+
status: s3(c.status)
|
|
2936
3035
|
}))
|
|
2937
3036
|
};
|
|
2938
3037
|
})
|
|
@@ -2944,6 +3043,37 @@ async function readModelCatalog(client, opts = {}) {
|
|
|
2944
3043
|
out.catalog_fingerprint = catalogFingerprint(out);
|
|
2945
3044
|
return out;
|
|
2946
3045
|
}
|
|
3046
|
+
async function resolveDeviceModel(client, req) {
|
|
3047
|
+
const files = await readDeviceModelFiles(client, {
|
|
3048
|
+
appInstanceIds: [req.app_instance_id],
|
|
3049
|
+
states: [.../* @__PURE__ */ new Set([...DEVICE_FILE_USABLE_STATES, ...DEVICE_OWN_FILE_USABLE_STATES])]
|
|
3050
|
+
});
|
|
3051
|
+
const own = files.find((f) => f.id === req.local_model_id);
|
|
3052
|
+
if (own) {
|
|
3053
|
+
if (own.artifact_id)
|
|
3054
|
+
throw new DeviceModelUnavailableError("not_own_file", `device model file ${own.id} is catalog artifact ${own.artifact_id}; pass its model id, not the file id`);
|
|
3055
|
+
if (!DEVICE_OWN_FILE_USABLE_STATES.includes(own.state))
|
|
3056
|
+
throw new DeviceModelUnavailableError("file_not_usable", `own model file ${own.id} on computer ${req.app_instance_id} is ${own.state}`);
|
|
3057
|
+
return { own: true, file: own };
|
|
3058
|
+
}
|
|
3059
|
+
const usable = files.filter((f) => DEVICE_FILE_USABLE_STATES.includes(f.state));
|
|
3060
|
+
const catalog = await readCatalogWith(client, { offeredOnly: false, devices: { appInstanceIds: [req.app_instance_id] } }, usable);
|
|
3061
|
+
const model = catalog.models.find((m) => m.id === req.local_model_id);
|
|
3062
|
+
if (!model)
|
|
3063
|
+
throw new DeviceModelUnavailableError("unknown_model", `no catalog model or own model file ${req.local_model_id} on computer ${req.app_instance_id}`);
|
|
3064
|
+
const label = model.common_name || model.name;
|
|
3065
|
+
if (req.variant_id && !model.variants.some((v) => v.id === req.variant_id))
|
|
3066
|
+
throw new DeviceModelUnavailableError("unknown_variant", `${label} has no listed variant ${req.variant_id}`);
|
|
3067
|
+
const held = (model.available_on ?? []).filter((a) => a.app_instance_id === req.app_instance_id && (!req.variant_id || a.variant_id === req.variant_id));
|
|
3068
|
+
const variants = held.map((a) => ({ a, v: model.variants.find((v) => v.id === a.variant_id) })).sort((x, y) => Number(y.v.is_default) - Number(x.v.is_default) || x.v.selection_rank - y.v.selection_rank || byCodepoint2(x.v.key, y.v.key));
|
|
3069
|
+
const pick = variants[0];
|
|
3070
|
+
if (!pick) {
|
|
3071
|
+
const which = req.variant_id ? ` (${model.variants.find((v) => v.id === req.variant_id).key})` : "";
|
|
3072
|
+
throw new DeviceModelUnavailableError("not_on_device", `computer ${req.app_instance_id} does not hold every required file of ${label}${which}`);
|
|
3073
|
+
}
|
|
3074
|
+
const byFileId = new Map(usable.map((f) => [f.id, f]));
|
|
3075
|
+
return { own: false, model, variant: pick.v, files: pick.a.device_model_file_ids.map((id) => byFileId.get(id)) };
|
|
3076
|
+
}
|
|
2947
3077
|
|
|
2948
3078
|
// models/fit.ts
|
|
2949
3079
|
var featuresOf = (c) => {
|