@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.
@@ -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 s3 = state();
418
- if (s3.reported.has(dedupeKey)) return;
419
- s3.reported.add(dedupeKey);
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 (s3.sink) s3.sink(entry);
422
- else if (!s3.announced) {
423
- s3.announced = true;
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((s3) => {
1097
- const entities = { ...s3.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({ ...s3, entities, lastFetched: Date.now(), error: null });
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((s3) => {
1113
- const existing = s3.entities[fresh.id];
1114
- return withIdLists({ ...s3, entities: { ...s3.entities, [fresh.id]: { ...emptyRecord(), ...existing, ...fresh, maker: fresh.maker ?? existing?.maker ?? null, _fetchType: "full" } } });
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((s3) => ({ ...s3, identityById: { ...s3.identityById, [id]: data } }));
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((s3) => ({ ...s3, modelClassIndex: buildModelClassIndex(rows3) })),
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((s3) => {
1142
- if (s3.modelClassStatus === "succeeded") return s3;
1152
+ (rows3) => update((s4) => {
1153
+ if (s4.modelClassStatus === "succeeded") return s4;
1143
1154
  const slice = buildModelClassIndex(rows3);
1144
- const base = s3.modelClassIndex ?? { classesByModelId: {}, classOfOffering: {} };
1145
- return { ...s3, modelClassIndex: { classesByModelId: { ...base.classesByModelId, ...slice.classesByModelId }, classOfOffering: { ...base.classOfOffering, ...slice.classOfOffering } } };
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((s3) => ({ ...s3, classConfigByOffering: { ...s3.classConfigByOffering, [key]: { modelId: cfg.modelId, controls: next?.controls ?? null, constraints: next?.constraints ?? null } } }));
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((s3) => ({ ...s3, isLoading: true, error: null }));
1216
+ update((s4) => ({ ...s4, isLoading: true, error: null }));
1206
1217
  try {
1207
1218
  const rows3 = await readOptions();
1208
- update((s3) => {
1209
- const entities = { ...s3.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
- ...s3,
1227
+ ...s4,
1217
1228
  entities,
1218
1229
  isLoading: false,
1219
1230
  error: null,
1220
- fetchScope: s3.fetchScope !== "all" && s3.fetchScope !== "deprecated" ? "active" : s3.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((s3) => ({ ...s3, isLoading: false, error: message }));
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((s3) => {
1238
- const detailErrorById = { ...s3.detailErrorById };
1248
+ update((s4) => {
1249
+ const detailErrorById = { ...s4.detailErrorById };
1239
1250
  delete detailErrorById[modelId];
1240
- return { ...s3, detailStatusById: { ...s3.detailStatusById, [modelId]: "loading" }, detailErrorById };
1251
+ return { ...s4, detailStatusById: { ...s4.detailStatusById, [modelId]: "loading" }, detailErrorById };
1241
1252
  });
1242
1253
  try {
1243
1254
  const record = await readFullModel(modelId);
1244
- update((s3) => {
1245
- const existing = s3.entities[record.id];
1246
- const detailErrorById = { ...s3.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
- ...s3,
1250
- detailStatusById: { ...s3.detailStatusById, [modelId]: "succeeded" },
1260
+ ...s4,
1261
+ detailStatusById: { ...s4.detailStatusById, [modelId]: "succeeded" },
1251
1262
  detailErrorById,
1252
1263
  entities: {
1253
- ...s3.entities,
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((s3) => ({
1262
- ...s3,
1263
- detailStatusById: { ...s3.detailStatusById, [modelId]: "failed" },
1264
- detailErrorById: { ...s3.detailErrorById, [modelId]: message }
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((s3) => {
1271
- const detailStatusById = { ...s3.detailStatusById };
1272
- const detailErrorById = { ...s3.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 { ...s3, detailStatusById, detailErrorById };
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((s3) => ({ ...s3, classConfigStatusByOffering: { ...s3.classConfigStatusByOffering, [key]: "loading" } }));
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((s3) => ({
1289
- ...s3,
1290
- classConfigStatusByOffering: { ...s3.classConfigStatusByOffering, [key]: "succeeded" },
1299
+ update((s4) => ({
1300
+ ...s4,
1301
+ classConfigStatusByOffering: { ...s4.classConfigStatusByOffering, [key]: "succeeded" },
1291
1302
  classConfigByOffering: {
1292
- ...s3.classConfigByOffering,
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((s3) => ({ ...s3, classConfigStatusByOffering: { ...s3.classConfigStatusByOffering, [key]: "failed" } }));
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((s3) => ({ ...s3, modelClassStatus: "loading" }));
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((s3) => ({ ...s3, modelClassStatus: "succeeded", modelClassIndex: index }));
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((s3) => ({ ...s3, modelClassStatus: "failed" }));
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((s3) => {
1329
- if (s3.modelClassStatus === "succeeded") return s3;
1330
- const base = s3.modelClassIndex ?? { classesByModelId: {}, classOfOffering: {} };
1339
+ update((s4) => {
1340
+ if (s4.modelClassStatus === "succeeded") return s4;
1341
+ const base = s4.modelClassIndex ?? { classesByModelId: {}, classOfOffering: {} };
1331
1342
  return {
1332
- ...s3,
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((s3) => ({ ...s3, identityStatusById: { ...s3.identityStatusById, [modelId]: "loading" } }));
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((s3) => ({
1357
- ...s3,
1358
- identityById: { ...s3.identityById, [row.id]: row, [modelId]: row },
1359
- identityStatusById: { ...s3.identityStatusById, [row.id]: "succeeded", [modelId]: "succeeded" }
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((s3) => ({ ...s3, identityStatusById: { ...s3.identityStatusById, [modelId]: "failed" } }));
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((s3) => ({ ...s3, identityStatusById: { ...s3.identityStatusById, ...marked } }));
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((s3) => ({
1390
- ...s3,
1391
- identityById: { ...s3.identityById, ...Object.fromEntries(rows3.map((r) => [r.id, r])) },
1400
+ update((s4) => ({
1401
+ ...s4,
1402
+ identityById: { ...s4.identityById, ...Object.fromEntries(rows3.map((r) => [r.id, r])) },
1392
1403
  identityStatusById: {
1393
- ...s3.identityStatusById,
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((s3) => ({
1400
- ...s3,
1401
- identityStatusById: { ...s3.identityStatusById, ...Object.fromEntries(wanted.map((id) => [id, "failed"])) }
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((s3) => {
1420
- if (s3.modelClassStatus === "succeeded") return s3;
1421
- const base = s3.modelClassIndex ?? { classesByModelId: {}, classOfOffering: {} };
1430
+ update((s4) => {
1431
+ if (s4.modelClassStatus === "succeeded") return s4;
1432
+ const base = s4.modelClassIndex ?? { classesByModelId: {}, classOfOffering: {} };
1422
1433
  return {
1423
- ...s3,
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((s3) => {
1443
- const existing = s3.entities[record.id];
1444
- const detailErrorById = { ...s3.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
- ...s3,
1448
- detailStatusById: { ...s3.detailStatusById, [modelId]: "succeeded" },
1458
+ ...s4,
1459
+ detailStatusById: { ...s4.detailStatusById, [modelId]: "succeeded" },
1449
1460
  detailErrorById,
1450
1461
  entities: {
1451
- ...s3.entities,
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((s3) => {
1462
- const entities = { ...s3.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({ ...s3, entities, fetchScope, lastFetched, isLoading: false, error: null });
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(s3) {
1489
- if (!activeKey || activeKey[0] !== s3.entities || activeKey[1] !== s3.activeIds) {
1490
- activeKey = [s3.entities, s3.activeIds];
1491
- activeOut = s3.activeIds.map((id) => s3.entities[id]).filter((m) => !!m);
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(s3) {
1498
- if (!deprecatedKey || deprecatedKey[0] !== s3.entities || deprecatedKey[1] !== s3.deprecatedIds) {
1499
- deprecatedKey = [s3.entities, s3.deprecatedIds];
1500
- deprecatedOut = s3.deprecatedIds.map((id) => s3.entities[id]).filter((m) => !!m);
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 = (s3) => allOf(s3.entities);
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 = (s3) => optionsOf(selectActiveModels(s3));
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 = (s3) => allOptionsOf(s3.entities);
1521
- var selectModelById = (s3, modelId) => modelId ? s3.entities[modelId] : void 0;
1522
- function selectModelLabelById(s3, modelId) {
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 = s3.entities[modelId];
1525
- const identity = s3.identityById[modelId];
1535
+ const m = s4.entities[modelId];
1536
+ const identity = s4.identityById[modelId];
1526
1537
  return modelDisplayLabel(m) || modelDisplayLabel(identity);
1527
1538
  }
1528
- var selectModelNameById = (s3, modelId) => modelId ? s3.entities[modelId]?.name : void 0;
1529
- var selectModelClassName = (s3, modelId, offeringId) => modelClassName(s3.modelClassIndex, modelId, offeringId);
1530
- function selectModelLabelWithClass(s3, modelId, offeringId) {
1531
- const label = selectModelLabelById(s3, modelId);
1532
- return label ? withModelClass(label, selectModelClassName(s3, modelId, offeringId)) : void 0;
1533
- }
1534
- var selectModelControls = (s3, modelId) => s3.entities[modelId]?.controls;
1535
- var selectModelFetchType = (s3, modelId) => s3.entities[modelId]?._fetchType;
1536
- var selectModelFullyLoaded = (s3, modelId) => !!modelId && s3.entities[modelId]?._fetchType === "full";
1537
- var selectModelDetailError = (s3, modelId) => modelId && s3.detailErrorById[modelId] || null;
1538
- var selectModelClassConfig = (s3, modelId, offeringId) => modelId && offeringId ? s3.classConfigByOffering[classConfigKey(modelId, offeringId)] : void 0;
1539
- var selectActiveModelsReady = (s3) => (s3.fetchScope === "active" || s3.fetchScope === "all") && s3.activeIds.length > 0;
1540
- var selectDeprecatedModelsReady = (s3) => (s3.fetchScope === "deprecated" || s3.fetchScope === "all") && s3.deprecatedIds.length > 0;
1541
- var selectAllModelsReady = (s3) => s3.fetchScope === "all";
1542
- var selectModelIdentityById = (s3, modelId) => s3.identityById[modelId];
1543
- var selectModelIdentityLookupStatus = (s3, modelId) => s3.identityStatusById[modelId] ?? "idle";
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 s3 = state2[variant];
1844
- if (s3.status === "idle" || s3.status === "error") return run(variant);
1845
- if (s3.status === "ready" && s3.loadedAt !== null && Date.now() - s3.loadedAt > staleAfterMs) return run(variant);
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(n2) {
2034
- const g = n2 / 2 ** 30;
2035
- if (g < 1) return `${Math.max(1, Math.round(n2 / 2 ** 20))} MB`;
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(n2) {
2363
- if (this.pos + n2 > this.size) throw new GgufError("the file ends inside its header");
2364
- if (this.pos + n2 > MAX_HEADER) throw new GgufError("its header is larger than any real model's");
2365
- if (this.pos >= this.base && this.pos + n2 <= this.base + this.buf.length) return;
2366
- const len = Math.min(Math.max(n2, CHUNK), this.size - this.pos);
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 < n2) throw new GgufError("the file ends inside its header");
2380
+ if (this.buf.length < n3) throw new GgufError("the file ends inside its header");
2370
2381
  }
2371
- async view(n2) {
2372
- await this.need(n2);
2373
- const v = new DataView(this.buf.buffer, this.buf.byteOffset + (this.pos - this.base), n2);
2374
- this.pos += n2;
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 n2 = (await this.view(8)).getBigUint64(0, true);
2382
- if (n2 > BigInt(Number.MAX_SAFE_INTEGER)) throw new GgufError("a size in its header is out of range");
2383
- return Number(n2);
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 s3 = new TextDecoder().decode(this.buf.subarray(at, at + len));
2401
+ const s4 = new TextDecoder().decode(this.buf.subarray(at, at + len));
2391
2402
  this.pos += len;
2392
- return s3;
2403
+ return s4;
2393
2404
  }
2394
- async skip(n2) {
2395
- if (this.pos + n2 > this.size) throw new GgufError("the file ends inside its header");
2396
- this.pos += n2;
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 n2 = await c.u64();
2449
- if (n2 > 64 * MAX_COUNT) throw new GgufError("an array in its header is out of range");
2450
- arrays[key] = n2;
2451
- if (elem === 8) for (let j = 0; j < n2; j++) await c.str();
2452
- else if (SCALAR_BYTES[elem] !== void 0) await c.skip(n2 * SCALAR_BYTES[elem]);
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 s3 = (k) => typeof h.kv[k] === "string" ? h.kv[k] : null;
2483
- const n2 = (k) => typeof h.kv[k] === "number" ? h.kv[k] : null;
2484
- const arch = s3("general.architecture");
2485
- const ft = n2("general.file_type");
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: s3("general.name"),
2500
+ name: s4("general.name"),
2490
2501
  quant: ft !== null ? FILE_TYPE_QUANT[ft] ?? `FTYPE_${ft}` : null,
2491
- context_length: arch ? n2(`${arch}.context_length`) : null,
2502
+ context_length: arch ? n3(`${arch}.context_length`) : null,
2492
2503
  parameter_count: params > 0 ? params : null,
2493
- split_count: n2("split.count") ?? 1,
2494
- split_index: n2("split.no") ?? 0,
2495
- type: s3("general.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((s3) => s3 !== null);
2547
+ const shards = names.map(shardName).filter((s4) => s4 !== null);
2537
2548
  if (!shards.length) return [];
2538
- const counts = [...new Set(shards.map((s3) => s3.count))];
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 n2 = counts[0];
2541
- const have = shards.map((s3) => s3.index);
2542
- const missing = Array.from({ length: n2 }, (_, i) => i + 1).filter((i) => !have.includes(i));
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 ${n2}`).join(", ")} ${missing.length === 1 ? "is" : "are"} missing`);
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((s3) => s3 !== primary && shardName(s3.name)?.stem === shard.stem)];
2583
- const problems = splitSetProblems(same.map((s3) => s3.name));
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((s3) => s3 !== primary && extOf(s3.name) === ".gguf" && isProjectorName(s3.name));
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 s2 = (v) => typeof v === "string" ? v : "";
2776
- var sn2 = (v) => typeof v === "string" ? v : null;
2777
- var n = (v) => typeof v === "number" && Number.isFinite(v) ? v : typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v)) ? Number(v) : null;
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) => [s2(r.id), 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(s2(v.model_id)) && (offeredOnly ? s2(v.review_state) === "offered" : isListedVariant(v)));
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 [artifacts, entries] = await Promise.all([
2804
- variants.length ? all("model_artifact", (q) => q.in("variant_id", variants.map((v) => s2(v.id)))) : Promise.resolve([]),
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 kindOfEntry = new Map(entries.map((e) => [s2(e.id), s2(e.kind)]));
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) => [s2(p.model_id), 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(s2(m.provider_id));
2924
+ const maker = providerById.get(s3(m.provider_id));
2829
2925
  return [
2830
2926
  {
2831
- id: s2(m.id),
2832
- name: s2(m.name),
2833
- desktop_id: sn2(m.metadata?.desktop_id),
2834
- common_name: sn2(m.common_name),
2835
- maker: sn2(maker?.name),
2836
- maker_logo_file_id: sn2(maker?.logo_file_id),
2837
- description: sn2(m.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: sn2(p.summary),
2844
- symbol: sn2(p.symbol),
2845
- color: sn2(p.color),
2846
- shelf: sn2(p.shelf),
2847
- shelf_title: sn2(p.shelf_title),
2848
- shelf_subtitle: sn2(p.shelf_subtitle),
2849
- display_order: n(p.display_order),
2850
- page: sn2(p.page),
2851
- params_label: sn2(p.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: sn2(p.thumbnail_file_id),
2855
- hero_file_id: sn2(p.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) => s2(v.model_id) === c.id).sort((a, b) => byCodepoint2(s2(a.key), s2(b.key))).map((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(s2(v.upstream_id));
2957
+ const up = upstreamById.get(s3(v.upstream_id));
2862
2958
  stamp(up);
2863
- const arts = artifacts.filter((a) => s2(a.variant_id) === s2(v.id)).sort((a, b) => (n(a.shard_index) ?? 0) - (n(b.shard_index) ?? 0) || byCodepoint2(s2(a.path_in_repo), s2(b.path_in_repo)));
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: s2(v.id),
2867
- key: s2(v.key),
2868
- catalog_key: `${s2(m.name)}/${s2(v.key)}`,
2869
- review_state: s2(v.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: s2(v.format),
2873
- quant: s2(v.quant),
2874
- architecture: s2(v.architecture),
2875
- chat_template_kind: s2(v.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: n(v.selection_rank) ?? Number.MAX_SAFE_INTEGER,
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: sn2(v.license_id),
2880
- gated: s2(v.gated),
2881
- total_bytes: n(v.total_bytes),
2882
- memory_bytes_min: n(v.memory_bytes_min),
2883
- memory_bytes_recommended: n(v.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: sn2(v.min_app_version),
2886
- upstream: up ? { kind: s2(up.upstream_kind), repo: sn2(up.repo), revision: sn2(up.revision) } : null,
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: s2(a.role),
2889
- path: s2(a.path_in_repo),
2890
- url: s2(a.url),
2891
- size_bytes: n(a.size_bytes) ?? 0,
2892
- sha256: s2(a.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: n(a.shard_index),
2895
- shard_count: n(a.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(s2(a.key), s2(b.key))).map((rt) => {
3005
+ runtimes: runtimes.sort((a, b) => byCodepoint2(s3(a.key), s3(b.key))).map((rt) => {
2907
3006
  stamp(rt);
2908
3007
  return {
2909
- key: s2(rt.key),
2910
- name: s2(rt.name),
2911
- upstream_repo: sn2(rt.upstream_repo),
2912
- builds: builds.filter((b) => s2(b.runtime_id) === s2(rt.id)).map((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: s2(b.id),
2917
- version: s2(b.runtime_version),
2918
- os: s2(b.os),
2919
- arch: s2(b.arch),
2920
- accelerator: s2(b.accelerator),
2921
- build_kind: s2(b.build_kind),
2922
- url: sn2(b.url),
2923
- size_bytes: n(b.size_bytes),
2924
- sha256: sn2(b.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: sn2(b.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) => s2(c.runtime_build_id) === s2(b.id)).map((c) => ({
2930
- hardware_class: s2(c.hardware_class),
2931
- format: s2(c.format),
2932
- quant: s2(c.quant),
2933
- architecture: s2(c.architecture),
2934
- chat_template_kind: s2(c.chat_template_kind),
2935
- status: s2(c.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) => {