@camstack/addon-model-studio 1.1.34 → 1.1.35
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/dist/{MotionZonesSettings-W4fQr3y3.mjs → MotionZonesSettings-c5QftjJr.mjs} +2 -2
- package/dist/{PrivacyMaskSettings-CtKzQjYD.mjs → PrivacyMaskSettings-D7-a5-3N.mjs} +4 -4
- package/dist/{SceneMonitorEditor-CM3z_N4Q.mjs → SceneMonitorEditor-CKP5eVgy.mjs} +3 -3
- package/dist/_stub.js +1740 -1018
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-BGvyoZxl.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-DA1xoR1n.mjs} +4 -4
- package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DlVrDcQA.mjs +26 -0
- package/dist/addon-model-studio.css +1 -1
- package/dist/{hostInit-BKRnEFn7.mjs → hostInit-DnVgXgR_.mjs} +3 -3
- package/dist/model-studio.addon.js +912 -31
- package/dist/model-studio.addon.mjs +912 -31
- package/dist/{player-overlays-CNkUzVZI.mjs → player-overlays-DQA3KJhP.mjs} +1 -1
- package/dist/remoteEntry.js +1 -1
- package/dist/{responsive-D-JIMJTX.mjs → responsive-B4KHcYzf.mjs} +1 -1
- package/dist/{square-CBwtzgNH.mjs → square-9pQrLRgw.mjs} +1 -1
- package/dist/{trash-2-CiJaurwE.mjs → trash-2-DRIA_3Ys.mjs} +1 -1
- package/dist/{use-device-snapshot-DpcJhtJX.mjs → use-device-snapshot-BTteNbQy.mjs} +1 -1
- package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-BqGMvm3d.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-BMAdzl3K.mjs} +1 -1
- package/package.json +1 -1
- package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Boi8PUUw.mjs +0 -26
|
@@ -34,7 +34,7 @@ let node_path$1 = __toESM(node_path, 1);
|
|
|
34
34
|
node_path = __toESM(node_path);
|
|
35
35
|
let node_util = require("node:util");
|
|
36
36
|
let node_zlib = require("node:zlib");
|
|
37
|
-
//#region ../system/dist/file-data-plane-
|
|
37
|
+
//#region ../system/dist/file-data-plane-BhKdxJgf.mjs
|
|
38
38
|
/** Build fetch headers, including HF auth token for huggingface.co URLs */
|
|
39
39
|
function buildHeaders(url) {
|
|
40
40
|
const headers = { "User-Agent": "CamStack/1.0" };
|
|
@@ -42,21 +42,56 @@ function buildHeaders(url) {
|
|
|
42
42
|
if (hfToken && url.includes("huggingface.co")) headers["Authorization"] = `Bearer ${hfToken}`;
|
|
43
43
|
return headers;
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
var DEFAULT_MAX_REDIRECTS = 5;
|
|
46
|
+
function normalizeDownloadOptions(third) {
|
|
47
|
+
if (typeof third === "function") return { onProgress: third };
|
|
48
|
+
return third ?? {};
|
|
49
|
+
}
|
|
50
|
+
function isRedirectStatus(status) {
|
|
51
|
+
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
52
|
+
}
|
|
53
|
+
function resolveRedirectUrl(current, location) {
|
|
54
|
+
return new URL(location, current);
|
|
55
|
+
}
|
|
56
|
+
async function downloadFile(url, destPath, onProgressOrOptions) {
|
|
52
57
|
if (node_fs$1.existsSync(destPath)) return destPath;
|
|
58
|
+
const opts = normalizeDownloadOptions(onProgressOrOptions);
|
|
59
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
60
|
+
const maxRedirects = opts.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
|
|
53
61
|
node_fs$1.mkdirSync(node_path$1.dirname(destPath), { recursive: true });
|
|
54
62
|
const tmpPath = destPath + ".downloading";
|
|
55
63
|
try {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
64
|
+
let current = url;
|
|
65
|
+
const seen = /* @__PURE__ */ new Set();
|
|
66
|
+
let response;
|
|
67
|
+
const manual = opts.redirectPolicy !== void 0;
|
|
68
|
+
for (let hop = 0; hop <= maxRedirects; hop++) {
|
|
69
|
+
const parsed = new URL(current);
|
|
70
|
+
if (opts.redirectPolicy) opts.redirectPolicy(parsed, hop);
|
|
71
|
+
if (seen.has(parsed.href)) throw new Error(`Redirect loop detected at ${parsed.href}`);
|
|
72
|
+
seen.add(parsed.href);
|
|
73
|
+
const controller = opts.timeoutMs !== void 0 ? new AbortController() : void 0;
|
|
74
|
+
const timer = controller && opts.timeoutMs !== void 0 ? setTimeout(() => controller.abort(), opts.timeoutMs) : void 0;
|
|
75
|
+
try {
|
|
76
|
+
response = await fetchImpl(current, {
|
|
77
|
+
redirect: manual ? "manual" : "follow",
|
|
78
|
+
headers: buildHeaders(current),
|
|
79
|
+
...controller ? { signal: controller.signal } : {}
|
|
80
|
+
});
|
|
81
|
+
} finally {
|
|
82
|
+
if (timer) clearTimeout(timer);
|
|
83
|
+
}
|
|
84
|
+
if (manual && isRedirectStatus(response.status)) {
|
|
85
|
+
const location = response.headers.get("location");
|
|
86
|
+
if (!location) throw new Error(`Redirect ${response.status} missing Location header`);
|
|
87
|
+
if (hop >= maxRedirects) throw new Error(`Too many redirects (max ${maxRedirects}) downloading ${url}`);
|
|
88
|
+
current = resolveRedirectUrl(current, location).href;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
if (!response) throw new Error(`No response downloading ${url}`);
|
|
94
|
+
if (manual && isRedirectStatus(response.status)) throw new Error(`Too many redirects (max ${maxRedirects}) downloading ${url}`);
|
|
60
95
|
if (!response.ok) throw new Error(`HTTP ${response.status} downloading ${url}`);
|
|
61
96
|
if (!response.body) throw new Error(`No response body from ${url}`);
|
|
62
97
|
const total = parseInt(response.headers.get("content-length") ?? "0", 10);
|
|
@@ -67,9 +102,10 @@ async function downloadFile(url, destPath, onProgress) {
|
|
|
67
102
|
for (;;) {
|
|
68
103
|
const { done, value } = await reader.read();
|
|
69
104
|
if (done || !value) break;
|
|
70
|
-
fileStream.write(value);
|
|
71
105
|
downloaded += value.length;
|
|
72
|
-
|
|
106
|
+
if (opts.maxBytes !== void 0 && downloaded > opts.maxBytes) throw new Error(`Downloaded ${downloaded} bytes exceeds the ${opts.maxBytes}-byte limit`);
|
|
107
|
+
fileStream.write(value);
|
|
108
|
+
opts.onProgress?.(downloaded, total);
|
|
73
109
|
}
|
|
74
110
|
} finally {
|
|
75
111
|
fileStream.end();
|
|
@@ -90,6 +126,495 @@ async function downloadFile(url, destPath, onProgress) {
|
|
|
90
126
|
(0, node_util.promisify)(node_zlib.brotliCompress);
|
|
91
127
|
(0, node_util.promisify)(node_zlib.gzip);
|
|
92
128
|
//#endregion
|
|
129
|
+
//#region src/scrypted/scrypted-download-policy.ts
|
|
130
|
+
/**
|
|
131
|
+
* Strict per-hop allow-list for Scrypted catalog downloads.
|
|
132
|
+
*
|
|
133
|
+
* Hosts are the ones observed from the pinned revision
|
|
134
|
+
* `187402b20a3be0a7fab9bceee21d857ed6ae0064` — not a generic CDN wildcard.
|
|
135
|
+
*/
|
|
136
|
+
var SCRYPTED_DOWNLOAD_REDIRECT_HOSTS = ["huggingface.co", "us.aws.cdn.hf.co"];
|
|
137
|
+
var LOCAL_HOSTS = new Set([
|
|
138
|
+
"localhost",
|
|
139
|
+
"127.0.0.1",
|
|
140
|
+
"::1",
|
|
141
|
+
"0.0.0.0"
|
|
142
|
+
]);
|
|
143
|
+
var IPV4_RE = /^(?:\d{1,3}\.){3}\d{1,3}$/;
|
|
144
|
+
function assertScryptedDownloadUrl(url, _hop) {
|
|
145
|
+
if (url.protocol !== "https:") throw new Error(`http downgrade refused: ${url.protocol}`);
|
|
146
|
+
if (url.username || url.password) throw new Error("userinfo not allowed on download URL");
|
|
147
|
+
const host = url.hostname.toLowerCase();
|
|
148
|
+
if (LOCAL_HOSTS.has(host) || IPV4_RE.test(host) || host.includes(":")) throw new Error(`host not allowed: ${host}`);
|
|
149
|
+
if (!SCRYPTED_DOWNLOAD_REDIRECT_HOSTS.includes(host)) throw new Error(`host not allowed: ${host}`);
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/scrypted/scrypted-yolo-manifest-data.json
|
|
153
|
+
var scrypted_yolo_manifest_data_default = /*#__PURE__*/ JSON.parse("[{\"id\":\"scrypted-yolov9t-relu-coreml\",\"label\":\"Scrypted YOLOv9T ReLU CoreML\",\"family\":\"yolov9t\",\"variant\":\"relu\",\"format\":\"coreml\",\"precision\":\"fp16\",\"files\":[{\"relativePath\":\"coreml/scrypted_yolov9t_relu/scrypted_yolov9t_relu.mlpackage/Manifest.json\",\"size\":617,\"sha256\":\"0a8733949d77e3204dba6418e81a11a8cc177015711457ce2c171266f23382d6\"},{\"relativePath\":\"coreml/scrypted_yolov9t_relu/scrypted_yolov9t_relu.mlpackage/Data/com.apple.CoreML/model.mlmodel\",\"size\":322229,\"sha256\":\"f5a62f196f1e72a84efa1637119dffb6501621af9cf6f867bc76b4b59c9242a0\"},{\"relativePath\":\"coreml/scrypted_yolov9t_relu/scrypted_yolov9t_relu.mlpackage/Data/com.apple.CoreML/weights/weight.bin\",\"size\":3797160,\"sha256\":\"b994419b8b68cfd6b89c3c6f3fe6b37458c86cc1d87fcca3df5034476a99dd38\"}],\"sizeMB\":4.12},{\"id\":\"scrypted-yolov9t-relu-onnx\",\"label\":\"Scrypted YOLOv9T ReLU ONNX\",\"family\":\"yolov9t\",\"variant\":\"relu\",\"format\":\"onnx\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"onnx/scrypted_yolov9t_relu/scrypted_yolov9t_relu.onnx\",\"size\":8499266,\"sha256\":\"3f11ea2071b27c285b58c16636a44d6a0a27bdd1ca8b64112dfadabd39cfea47\"}],\"sizeMB\":8.5},{\"id\":\"scrypted-yolov9t-relu-test-coreml\",\"label\":\"Scrypted YOLOv9T ReLU test CoreML\",\"family\":\"yolov9t\",\"variant\":\"relu_test\",\"format\":\"coreml\",\"precision\":\"fp16\",\"files\":[{\"relativePath\":\"coreml/scrypted_yolov9t_relu_test/scrypted_yolov9t_relu_test.mlpackage/Manifest.json\",\"size\":617,\"sha256\":\"a5e0128b643f1a66f3e07d4b332f9ccbca97ccab75651c794f0b9f1fb41c8491\"},{\"relativePath\":\"coreml/scrypted_yolov9t_relu_test/scrypted_yolov9t_relu_test.mlpackage/Data/com.apple.CoreML/model.mlmodel\",\"size\":322229,\"sha256\":\"79f4f647f2a0c79db3b560c84251b48c7ba7cbe9c1fecc9674d1a603a389dfa3\"},{\"relativePath\":\"coreml/scrypted_yolov9t_relu_test/scrypted_yolov9t_relu_test.mlpackage/Data/com.apple.CoreML/weights/weight.bin\",\"size\":3797160,\"sha256\":\"72efc9dc6af9241b5e5ee7de24a4f55741f6425d0e279d797f0f223e3d355f84\"}],\"sizeMB\":4.12},{\"id\":\"scrypted-yolov9t-relu-test-onnx\",\"label\":\"Scrypted YOLOv9T ReLU test ONNX\",\"family\":\"yolov9t\",\"variant\":\"relu_test\",\"format\":\"onnx\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"onnx/scrypted_yolov9t_relu_test/scrypted_yolov9t_relu_test.onnx\",\"size\":8499266,\"sha256\":\"d20aa0e6923a3af7f5e84e3a6d28fb00e8ecd592e3225f986f93bd34b634e2f8\"}],\"sizeMB\":8.5},{\"id\":\"scrypted-yolov9s-relu-coreml\",\"label\":\"Scrypted YOLOv9S ReLU CoreML\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"coreml\",\"precision\":\"fp16\",\"files\":[{\"relativePath\":\"coreml/scrypted_yolov9s_relu/scrypted_yolov9s_relu.mlpackage/Manifest.json\",\"size\":617,\"sha256\":\"3bcdb820b82f1e89266f2d11bae1604d3ee4ca6d11dc5bd5c57055ff4157e9f3\"},{\"relativePath\":\"coreml/scrypted_yolov9s_relu/scrypted_yolov9s_relu.mlpackage/Data/com.apple.CoreML/model.mlmodel\",\"size\":322664,\"sha256\":\"9a9c3c9cbca406ec2f0418ec867ed6b878ec6d02c16636d30e0bbd83d2ff7c40\"},{\"relativePath\":\"coreml/scrypted_yolov9s_relu/scrypted_yolov9s_relu.mlpackage/Data/com.apple.CoreML/weights/weight.bin\",\"size\":14190120,\"sha256\":\"fd51eda58a993997268f6a2e424b536ba173207ad7974ab8bee2a8ff76d52dab\"}],\"sizeMB\":14.51},{\"id\":\"scrypted-yolov9s-relu-onnx\",\"label\":\"Scrypted YOLOv9S ReLU ONNX\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"onnx\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"onnx/scrypted_yolov9s_relu/scrypted_yolov9s_relu.onnx\",\"size\":29287339,\"sha256\":\"ca0b5bd98d5f59eb2029c6cb5129d53af59873f62a317a028beda463bbb96399\"}],\"sizeMB\":29.29},{\"id\":\"scrypted-yolov9s-relu-test-coreml\",\"label\":\"Scrypted YOLOv9S ReLU test CoreML\",\"family\":\"yolov9s\",\"variant\":\"relu_test\",\"format\":\"coreml\",\"precision\":\"fp16\",\"files\":[{\"relativePath\":\"coreml/scrypted_yolov9s_relu_test/scrypted_yolov9s_relu_test.mlpackage/Manifest.json\",\"size\":617,\"sha256\":\"e2d24de0ce7e8573f19572029765e55abc714f36a85175ad7fcb95c001a76653\"},{\"relativePath\":\"coreml/scrypted_yolov9s_relu_test/scrypted_yolov9s_relu_test.mlpackage/Data/com.apple.CoreML/model.mlmodel\",\"size\":322664,\"sha256\":\"186b147cdfd15c5ed7a381ec3a426a22cee0b5c52d56f14efd26da06357f5cb0\"},{\"relativePath\":\"coreml/scrypted_yolov9s_relu_test/scrypted_yolov9s_relu_test.mlpackage/Data/com.apple.CoreML/weights/weight.bin\",\"size\":14190120,\"sha256\":\"e50651924c2cbffd56008a8953569e66c3de69f21807fcf7d825390ce30f21f8\"}],\"sizeMB\":14.51},{\"id\":\"scrypted-yolov9s-relu-test-onnx\",\"label\":\"Scrypted YOLOv9S ReLU test ONNX\",\"family\":\"yolov9s\",\"variant\":\"relu_test\",\"format\":\"onnx\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"onnx/scrypted_yolov9s_relu_test/scrypted_yolov9s_relu_test.onnx\",\"size\":29287339,\"sha256\":\"115ddb83333762a0f2599421335678696454a5c3571ade5df9bac8e9cb5fb4cf\"}],\"sizeMB\":29.29},{\"id\":\"scrypted-yolov9m-relu-coreml\",\"label\":\"Scrypted YOLOv9M ReLU CoreML\",\"family\":\"yolov9m\",\"variant\":\"relu\",\"format\":\"coreml\",\"precision\":\"fp16\",\"files\":[{\"relativePath\":\"coreml/scrypted_yolov9m_relu/scrypted_yolov9m_relu.mlpackage/Manifest.json\",\"size\":617,\"sha256\":\"c548c6e1ff768b2b01bd71e230aa712a1635ae2baa30e0369557a42fc531ecbf\"},{\"relativePath\":\"coreml/scrypted_yolov9m_relu/scrypted_yolov9m_relu.mlpackage/Data/com.apple.CoreML/model.mlmodel\",\"size\":247061,\"sha256\":\"1c40e02fc2a2559cb906ea6cd9da9c730d4a78f5019290cbb38d2fcaae379fe7\"},{\"relativePath\":\"coreml/scrypted_yolov9m_relu/scrypted_yolov9m_relu.mlpackage/Data/com.apple.CoreML/weights/weight.bin\",\"size\":39880616,\"sha256\":\"4cf938e5bfcc3ee1644813e4eb4702a7de450cc0581df94c719cf43d0ed2e408\"}],\"sizeMB\":40.13},{\"id\":\"scrypted-yolov9m-relu-onnx\",\"label\":\"Scrypted YOLOv9M ReLU ONNX\",\"family\":\"yolov9m\",\"variant\":\"relu\",\"format\":\"onnx\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"onnx/scrypted_yolov9m_relu/scrypted_yolov9m_relu.onnx\",\"size\":80380404,\"sha256\":\"e7a81fb33224a0105d2a0a594c3fa43c398c361de623f1d14156ae826e7312d5\"}],\"sizeMB\":80.38},{\"id\":\"scrypted-yolov9m-relu-test-coreml\",\"label\":\"Scrypted YOLOv9M ReLU test CoreML\",\"family\":\"yolov9m\",\"variant\":\"relu_test\",\"format\":\"coreml\",\"precision\":\"fp16\",\"files\":[{\"relativePath\":\"coreml/scrypted_yolov9m_relu_test/scrypted_yolov9m_relu_test.mlpackage/Manifest.json\",\"size\":617,\"sha256\":\"8e2684b286d02b4bdf9db1027c84cb89bf8e4830cfb58269f3ec457c5a010650\"},{\"relativePath\":\"coreml/scrypted_yolov9m_relu_test/scrypted_yolov9m_relu_test.mlpackage/Data/com.apple.CoreML/model.mlmodel\",\"size\":247061,\"sha256\":\"53d5946e74b13b15c5f8616081c9f15e8a95a5df3d5b45b830fc5a7f49561c16\"},{\"relativePath\":\"coreml/scrypted_yolov9m_relu_test/scrypted_yolov9m_relu_test.mlpackage/Data/com.apple.CoreML/weights/weight.bin\",\"size\":39880616,\"sha256\":\"69774bf0032c37707601b8cdc1e1820dbd9f8288eb5d6f3879618a5d34ff5199\"}],\"sizeMB\":40.13},{\"id\":\"scrypted-yolov9m-relu-test-onnx\",\"label\":\"Scrypted YOLOv9M ReLU test ONNX\",\"family\":\"yolov9m\",\"variant\":\"relu_test\",\"format\":\"onnx\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"onnx/scrypted_yolov9m_relu_test/scrypted_yolov9m_relu_test.onnx\",\"size\":80380404,\"sha256\":\"8bb054f099295909ec518e2a6598d63db24d3f90edc8105a0f016903f2a205a2\"}],\"sizeMB\":80.38},{\"id\":\"scrypted-yolov9c-relu-coreml\",\"label\":\"Scrypted YOLOv9C ReLU CoreML\",\"family\":\"yolov9c\",\"variant\":\"relu\",\"format\":\"coreml\",\"precision\":\"fp16\",\"files\":[{\"relativePath\":\"coreml/scrypted_yolov9c_relu/scrypted_yolov9c_relu.mlpackage/Manifest.json\",\"size\":617,\"sha256\":\"813a306ebd69cabfd31f00434f5a961feeca7d237833f3e11805dcf4ad120ba9\"},{\"relativePath\":\"coreml/scrypted_yolov9c_relu/scrypted_yolov9c_relu.mlpackage/Data/com.apple.CoreML/model.mlmodel\",\"size\":263210,\"sha256\":\"862f082619075e470738306b0ff0a37ae70a7d08e684ca6a6dfa1971d404e0c3\"},{\"relativePath\":\"coreml/scrypted_yolov9c_relu/scrypted_yolov9c_relu.mlpackage/Data/com.apple.CoreML/weights/weight.bin\",\"size\":50489960,\"sha256\":\"ed89747e58943758e2a87221d1d404f6a4466cbc039c3c6f96e327cadc403d27\"}],\"sizeMB\":50.75},{\"id\":\"scrypted-yolov9c-relu-onnx\",\"label\":\"Scrypted YOLOv9C ReLU ONNX\",\"family\":\"yolov9c\",\"variant\":\"relu\",\"format\":\"onnx\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"onnx/scrypted_yolov9c_relu/scrypted_yolov9c_relu.onnx\",\"size\":101631962,\"sha256\":\"b9372c4428daf4cd944c114b1e80d1a9ff0487d743e53a77216a051cfbef7116\"}],\"sizeMB\":101.63},{\"id\":\"scrypted-yolov9c-relu-test-coreml\",\"label\":\"Scrypted YOLOv9C ReLU test CoreML\",\"family\":\"yolov9c\",\"variant\":\"relu_test\",\"format\":\"coreml\",\"precision\":\"fp16\",\"files\":[{\"relativePath\":\"coreml/scrypted_yolov9c_relu_test/scrypted_yolov9c_relu_test.mlpackage/Manifest.json\",\"size\":617,\"sha256\":\"966ef82a93cb0ccf1036a8c9729c065fd372fa9590a97d0945e581cc674f7ad2\"},{\"relativePath\":\"coreml/scrypted_yolov9c_relu_test/scrypted_yolov9c_relu_test.mlpackage/Data/com.apple.CoreML/model.mlmodel\",\"size\":263210,\"sha256\":\"08718ecd3e9552d21a702bd8df60911cad0eec7b2ae382466809550f14634711\"},{\"relativePath\":\"coreml/scrypted_yolov9c_relu_test/scrypted_yolov9c_relu_test.mlpackage/Data/com.apple.CoreML/weights/weight.bin\",\"size\":50489960,\"sha256\":\"fe5936b9430ab4c87f30b148220131d6ec998b2377223d033e2fa2a00e60aada\"}],\"sizeMB\":50.75},{\"id\":\"scrypted-yolov9c-relu-test-onnx\",\"label\":\"Scrypted YOLOv9C ReLU test ONNX\",\"family\":\"yolov9c\",\"variant\":\"relu_test\",\"format\":\"onnx\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"onnx/scrypted_yolov9c_relu_test/scrypted_yolov9c_relu_test.onnx\",\"size\":101631962,\"sha256\":\"2f4e2031e8efb4cbc871594338b2e875d78ab4af3ee0258d253273b1aac84742\"}],\"sizeMB\":101.63},{\"id\":\"scrypted-yolov9t-relu-openvino-fp32\",\"label\":\"Scrypted YOLOv9T ReLU OpenVINO FP32\",\"family\":\"yolov9t\",\"variant\":\"relu\",\"format\":\"openvino\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9t_relu/best-converted.xml\",\"size\":628222,\"sha256\":\"01b5a9d02a0f281e1aaf4860e1b0ed3a7f16b9d78975be8ff985ae0ee283d877\"},{\"relativePath\":\"openvino/scrypted_yolov9t_relu/best-converted.bin\",\"size\":7542204,\"sha256\":\"4fe9ce8dbdf94514fdd450cbfc907f257153e4898a0c1489a44e4912d8e32cc7\"}],\"sizeMB\":8.17},{\"id\":\"scrypted-yolov9s-relu-openvino-fp32\",\"label\":\"Scrypted YOLOv9S ReLU OpenVINO FP32\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"openvino\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9s_relu/best-converted.xml\",\"size\":629249,\"sha256\":\"5b4a229bf7229425698b63bd065a403e440a6151a6ddff0034867659cc4a480d\"},{\"relativePath\":\"openvino/scrypted_yolov9s_relu/best-converted.bin\",\"size\":28329724,\"sha256\":\"c030bd219208ea94082578c81f383d4ef9ec45d7150eec1c2c2ff2569cf49b35\"}],\"sizeMB\":28.96},{\"id\":\"scrypted-yolov9m-relu-openvino-fp32\",\"label\":\"Scrypted YOLOv9M ReLU OpenVINO FP32\",\"family\":\"yolov9m\",\"variant\":\"relu\",\"format\":\"openvino\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9m_relu/best-converted.xml\",\"size\":485439,\"sha256\":\"c66f9f1965bf70fd3635d4dfc3cb94c72c06b344c256713237d77706725b9a0c\"},{\"relativePath\":\"openvino/scrypted_yolov9m_relu/best-converted.bin\",\"size\":79717372,\"sha256\":\"dfc2d8952bbca9ad0e116168e3fd5c754e601c327d78c4f4d0d4cae12f0e1d2a\"}],\"sizeMB\":80.2},{\"id\":\"scrypted-yolov9c-relu-openvino-fp32\",\"label\":\"Scrypted YOLOv9C ReLU OpenVINO FP32\",\"family\":\"yolov9c\",\"variant\":\"relu\",\"format\":\"openvino\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9c_relu/best-converted.xml\",\"size\":535047,\"sha256\":\"836c1e998ebf76108ff64d554fdfa07aa72c42624f1a5448701bb409f27b8d68\"},{\"relativePath\":\"openvino/scrypted_yolov9c_relu/best-converted.bin\",\"size\":100942972,\"sha256\":\"11a1b330ac9843b349927977b3c7c7f17bb42af73a7ff102969132f27ab81814\"}],\"sizeMB\":101.48},{\"id\":\"scrypted-yolov9t-relu-openvino-int8\",\"label\":\"Scrypted YOLOv9T ReLU OpenVINO INT8\",\"family\":\"yolov9t\",\"variant\":\"relu\",\"format\":\"openvino\",\"precision\":\"int8\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9t_relu_int8/best-converted.xml\",\"size\":1380379,\"sha256\":\"e5a76bf88ac69fc0ebc50c04b61ff91550dd9c2349af1f6d7e4cac949c640cec\"},{\"relativePath\":\"openvino/scrypted_yolov9t_relu_int8/best-converted.bin\",\"size\":1954956,\"sha256\":\"98fb5b6826d7c6697a585f69ba96754873772d9d61fbed8335aab761e8248659\"}],\"sizeMB\":3.34},{\"id\":\"scrypted-yolov9t-relu-test-openvino-int8\",\"label\":\"Scrypted YOLOv9T ReLU test OpenVINO INT8\",\"family\":\"yolov9t\",\"variant\":\"relu_test\",\"format\":\"openvino\",\"precision\":\"int8\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9t_relu_test_int8/best-converted.xml\",\"size\":1380383,\"sha256\":\"9e4119284e923e08c9929e78fd31e2552e602a552ac538ae99bf34a50dd4d172\"},{\"relativePath\":\"openvino/scrypted_yolov9t_relu_test_int8/best-converted.bin\",\"size\":1954960,\"sha256\":\"efe5b0a2fd2f5ddef5923eeb9e592a7ef14719061bb7605c86e05b35c2c58ea9\"}],\"sizeMB\":3.34},{\"id\":\"scrypted-yolov9s-relu-openvino-int8\",\"label\":\"Scrypted YOLOv9S ReLU OpenVINO INT8\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"openvino\",\"precision\":\"int8\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9s_relu_int8/best-converted.xml\",\"size\":1382088,\"sha256\":\"4e6c24cf24afbab21462f7e1a15bd05b1cbbe5915046000e03c2435d8110bbbf\"},{\"relativePath\":\"openvino/scrypted_yolov9s_relu_int8/best-converted.bin\",\"size\":7197308,\"sha256\":\"23fd3d666a938c41db232d9c6239baec1134716cfb4ed4ddd3d602dd20eb8ac0\"}],\"sizeMB\":8.58},{\"id\":\"scrypted-yolov9s-relu-test-openvino-int8\",\"label\":\"Scrypted YOLOv9S ReLU test OpenVINO INT8\",\"family\":\"yolov9s\",\"variant\":\"relu_test\",\"format\":\"openvino\",\"precision\":\"int8\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9s_relu_test_int8/best-converted.xml\",\"size\":1382092,\"sha256\":\"49865bfeafe7576afce089ba6f5c3e25a9192c8b514eb96265c90fe877795641\"},{\"relativePath\":\"openvino/scrypted_yolov9s_relu_test_int8/best-converted.bin\",\"size\":7197312,\"sha256\":\"78e9d36e6b74754db2b624ea3ec0b3ff57ed6dfed59a4f2225e1a271054597de\"}],\"sizeMB\":8.58},{\"id\":\"scrypted-yolov9m-relu-openvino-int8\",\"label\":\"Scrypted YOLOv9M ReLU OpenVINO INT8\",\"family\":\"yolov9m\",\"variant\":\"relu\",\"format\":\"openvino\",\"precision\":\"int8\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9m_relu_int8/best-converted.xml\",\"size\":1024595,\"sha256\":\"1242ab8d8c73b2e80c5dd541177ade6e102a2684114b059136acb4cad6efb034\"},{\"relativePath\":\"openvino/scrypted_yolov9m_relu_int8/best-converted.bin\",\"size\":20090516,\"sha256\":\"da8e44af8bee66478e6f359e9504bfb13ab4b76f4bd8a2f1b10cfbc22febcf6e\"}],\"sizeMB\":21.12},{\"id\":\"scrypted-yolov9m-relu-test-openvino-int8\",\"label\":\"Scrypted YOLOv9M ReLU test OpenVINO INT8\",\"family\":\"yolov9m\",\"variant\":\"relu_test\",\"format\":\"openvino\",\"precision\":\"int8\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9m_relu_test_int8/best-converted.xml\",\"size\":1024601,\"sha256\":\"24395ae5fdbb321304c865372cd2b905cdc55abb7610520f3963702e2371decb\"},{\"relativePath\":\"openvino/scrypted_yolov9m_relu_test_int8/best-converted.bin\",\"size\":20090520,\"sha256\":\"4360fe0312067f92c49f180c7b8a10271f14d58b8909aa9b96b13af4ab9289aa\"}],\"sizeMB\":21.12},{\"id\":\"scrypted-yolov9c-relu-openvino-int8\",\"label\":\"Scrypted YOLOv9C ReLU OpenVINO INT8\",\"family\":\"yolov9c\",\"variant\":\"relu\",\"format\":\"openvino\",\"precision\":\"int8\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9c_relu_int8/best-converted.xml\",\"size\":1092695,\"sha256\":\"a45c62940120c4301d8fff9fb3b275c29e3618f403ea68a785f2f3220cefec7a\"},{\"relativePath\":\"openvino/scrypted_yolov9c_relu_int8/best-converted.bin\",\"size\":25428280,\"sha256\":\"0643403a56178c3ed1e066c7884458d8ea6780115d662bdc38d10754d7a11c79\"}],\"sizeMB\":26.52},{\"id\":\"scrypted-yolov9c-relu-test-openvino-int8\",\"label\":\"Scrypted YOLOv9C ReLU test OpenVINO INT8\",\"family\":\"yolov9c\",\"variant\":\"relu_test\",\"format\":\"openvino\",\"precision\":\"int8\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9c_relu_test_int8/best-converted.xml\",\"size\":1092695,\"sha256\":\"a8a1899848ebd060af2326d3c8d3c09ea83354f0bedff6cce6fa08b284ea5519\"},{\"relativePath\":\"openvino/scrypted_yolov9c_relu_test_int8/best-converted.bin\",\"size\":25428280,\"sha256\":\"2eb6bb3d0a03a9b239b6f623f9e7bced62eea61fbe4d8ff89c1b3e2e2b7978a2\"}],\"sizeMB\":26.52},{\"id\":\"scrypted-yolov9s-relu-sep-320-tflite-float16\",\"label\":\"Scrypted YOLOv9s ReLU sep 320 TFLite float16\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"tflite\",\"precision\":\"float16\",\"files\":[{\"relativePath\":\"tflite/scrypted_yolov9s_relu_sep_320/best_float16.tflite\",\"size\":14482836,\"sha256\":\"04889db3b81af361c361e41ffa99327330d37b71d680a2f889e6cdf1e94c0d8c\"}],\"sizeMB\":14.48},{\"id\":\"scrypted-yolov9s-relu-sep-320-tflite-float32\",\"label\":\"Scrypted YOLOv9s ReLU sep 320 TFLite float32\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"tflite\",\"precision\":\"float32\",\"files\":[{\"relativePath\":\"tflite/scrypted_yolov9s_relu_sep_320/best_float32.tflite\",\"size\":28777068,\"sha256\":\"f5d7d08fc1d8f2262633d2df4391638e0481740331aaa1837880a47c33001a96\"}],\"sizeMB\":28.78},{\"id\":\"scrypted-yolov9s-relu-sep-320-tflite-int8\",\"label\":\"Scrypted YOLOv9s ReLU sep 320 TFLite int8\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"tflite\",\"precision\":\"int8\",\"files\":[{\"relativePath\":\"tflite/scrypted_yolov9s_relu_sep_320/best_int8.tflite\",\"size\":7325616,\"sha256\":\"1e9866b16fe80931383bf3c94dc629fe93a37ba3dc1465ea019eb0a50bd428df\"}],\"sizeMB\":7.33},{\"id\":\"scrypted-yolov9s-relu-sep-320-tflite-integer_quant\",\"label\":\"Scrypted YOLOv9s ReLU sep 320 TFLite integer_quant\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"tflite\",\"precision\":\"integer_quant\",\"files\":[{\"relativePath\":\"tflite/scrypted_yolov9s_relu_sep_320/best_integer_quant.tflite\",\"size\":7342552,\"sha256\":\"bc1c902e9a9865637ee2403f70c6bf5c29c695d9163e58ad658feb29bb097ebc\"}],\"sizeMB\":7.34},{\"id\":\"scrypted-yolov9s-relu-sep-320-tflite-full_integer_quant\",\"label\":\"Scrypted YOLOv9s ReLU sep 320 TFLite full_integer_quant\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"tflite\",\"precision\":\"full_integer_quant\",\"files\":[{\"relativePath\":\"tflite/scrypted_yolov9s_relu_sep_320/best_full_integer_quant.tflite\",\"size\":7341704,\"sha256\":\"483ce38dbe33ee5a35f40a82f56c77629a44fa5d95efd8ade9fa97e4d2a261fd\"}],\"sizeMB\":7.34},{\"id\":\"scrypted-yolov9s-relu-sep-320-tflite-edgetpu\",\"label\":\"Scrypted YOLOv9s ReLU sep 320 TFLite edgetpu\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"tflite\",\"precision\":\"edgetpu\",\"files\":[{\"relativePath\":\"tflite/scrypted_yolov9s_relu_sep_320/best_full_integer_quant_edgetpu.tflite\",\"size\":8164480,\"sha256\":\"e1e6b074a3b9fdf19a171baf615553ad375a046aa7de2a3fef9a1950175fab22\"}],\"sizeMB\":8.16}]");
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/scrypted/scrypted-yolo-manifest.ts
|
|
156
|
+
var SCRYPTED_SHA256_RE = /^[0-9a-f]{64}$/;
|
|
157
|
+
var SCRYPTED_HF_HOST = "https://huggingface.co";
|
|
158
|
+
var SCRYPTED_HF_REPO = "scrypted/plugin-models";
|
|
159
|
+
var SCRYPTED_HF_REVISION = "187402b20a3be0a7fab9bceee21d857ed6ae0064";
|
|
160
|
+
var SCRYPTED_YOLO_LABELS = [
|
|
161
|
+
"person",
|
|
162
|
+
"vehicle",
|
|
163
|
+
"animal"
|
|
164
|
+
];
|
|
165
|
+
var SCRYPTED_IDENTITY_CLASS_MAP = {
|
|
166
|
+
mapping: {
|
|
167
|
+
person: "person",
|
|
168
|
+
vehicle: "vehicle",
|
|
169
|
+
animal: "animal"
|
|
170
|
+
},
|
|
171
|
+
preserveOriginal: false
|
|
172
|
+
};
|
|
173
|
+
var SCRYPTED_LABEL_DEFS = SCRYPTED_YOLO_LABELS.map((id) => ({
|
|
174
|
+
id,
|
|
175
|
+
name: id
|
|
176
|
+
}));
|
|
177
|
+
var MLPACKAGE_INNER_FILES = [
|
|
178
|
+
"Manifest.json",
|
|
179
|
+
"Data/com.apple.CoreML/model.mlmodel",
|
|
180
|
+
"Data/com.apple.CoreML/weights/weight.bin"
|
|
181
|
+
];
|
|
182
|
+
var INPUT_SIZE = {
|
|
183
|
+
width: 320,
|
|
184
|
+
height: 320
|
|
185
|
+
};
|
|
186
|
+
function assertScryptedFileSha256(value, relativePath) {
|
|
187
|
+
if (value === void 0 || !SCRYPTED_SHA256_RE.test(value)) throw new Error(`Invalid sha256 for ${relativePath}: expected 64 lowercase hex`);
|
|
188
|
+
return value;
|
|
189
|
+
}
|
|
190
|
+
function assertScryptedRelativePath(value) {
|
|
191
|
+
if (value.length === 0 || value.startsWith("/") || value.includes("\\") || value.includes("\0")) throw new Error(`Unsafe Scrypted relative path: ${value}`);
|
|
192
|
+
if (value.split("/").some((segment) => segment.length === 0 || segment === "." || segment === "..")) throw new Error(`Unsafe Scrypted relative path: ${value}`);
|
|
193
|
+
return value;
|
|
194
|
+
}
|
|
195
|
+
function buildScryptedResolveUrl(relativePath) {
|
|
196
|
+
return `${SCRYPTED_HF_HOST}/${SCRYPTED_HF_REPO}/resolve/${SCRYPTED_HF_REVISION}/${assertScryptedRelativePath(relativePath).split("/").map((segment) => encodeURIComponent(segment)).join("/")}`;
|
|
197
|
+
}
|
|
198
|
+
function packageDirectory(files) {
|
|
199
|
+
const manifest = files.find((file) => file.relativePath.endsWith("/Manifest.json"));
|
|
200
|
+
if (!manifest) throw new Error("CoreML entry is missing Manifest.json");
|
|
201
|
+
return assertScryptedRelativePath(manifest.relativePath.replace(/\/Manifest\.json$/, ""));
|
|
202
|
+
}
|
|
203
|
+
function buildModel(spec) {
|
|
204
|
+
const group = {
|
|
205
|
+
family: "yolov9",
|
|
206
|
+
tier: spec.family.slice(-1),
|
|
207
|
+
resolution: 320,
|
|
208
|
+
...spec.variant === "relu_test" ? { optimization: "fast" } : {},
|
|
209
|
+
...spec.precision === "int8" ? { precision: "int8" } : {}
|
|
210
|
+
};
|
|
211
|
+
if (spec.format === "coreml") {
|
|
212
|
+
const dir = packageDirectory(spec.files);
|
|
213
|
+
return {
|
|
214
|
+
id: spec.id,
|
|
215
|
+
name: spec.label,
|
|
216
|
+
description: `${spec.label} — imported from the local Scrypted YOLO catalog.`,
|
|
217
|
+
formats: { coreml: {
|
|
218
|
+
url: buildScryptedResolveUrl(dir),
|
|
219
|
+
sizeMB: spec.sizeMB,
|
|
220
|
+
isDirectory: true,
|
|
221
|
+
files: [...MLPACKAGE_INNER_FILES],
|
|
222
|
+
runtimes: ["python"]
|
|
223
|
+
} },
|
|
224
|
+
inputSize: INPUT_SIZE,
|
|
225
|
+
labels: SCRYPTED_LABEL_DEFS,
|
|
226
|
+
preprocessMode: "resize",
|
|
227
|
+
classMap: SCRYPTED_IDENTITY_CLASS_MAP,
|
|
228
|
+
group
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (spec.format === "openvino") {
|
|
232
|
+
const xml = spec.files.find((file) => file.relativePath.endsWith(".xml"));
|
|
233
|
+
const bin = spec.files.find((file) => file.relativePath.endsWith(".bin"));
|
|
234
|
+
if (!xml || !bin) throw new Error(`OpenVINO entry ${spec.id} is missing xml/bin`);
|
|
235
|
+
const binName = bin.relativePath.split("/").pop() ?? "best-converted.bin";
|
|
236
|
+
return {
|
|
237
|
+
id: spec.id,
|
|
238
|
+
name: spec.label,
|
|
239
|
+
description: `${spec.label} — imported from the local Scrypted YOLO catalog.`,
|
|
240
|
+
formats: { openvino: {
|
|
241
|
+
url: buildScryptedResolveUrl(xml.relativePath),
|
|
242
|
+
sizeMB: spec.sizeMB,
|
|
243
|
+
files: [binName],
|
|
244
|
+
runtimes: ["python"]
|
|
245
|
+
} },
|
|
246
|
+
inputSize: INPUT_SIZE,
|
|
247
|
+
labels: SCRYPTED_LABEL_DEFS,
|
|
248
|
+
preprocessMode: "resize",
|
|
249
|
+
classMap: SCRYPTED_IDENTITY_CLASS_MAP,
|
|
250
|
+
group
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const main = spec.files[0];
|
|
254
|
+
if (!main) throw new Error(`Entry ${spec.id} has no files`);
|
|
255
|
+
return {
|
|
256
|
+
id: spec.id,
|
|
257
|
+
name: spec.label,
|
|
258
|
+
description: `${spec.label} — imported from the local Scrypted YOLO catalog.`,
|
|
259
|
+
formats: { [spec.format]: {
|
|
260
|
+
url: buildScryptedResolveUrl(main.relativePath),
|
|
261
|
+
sizeMB: spec.sizeMB,
|
|
262
|
+
runtimes: spec.format === "tflite" ? ["python"] : void 0
|
|
263
|
+
} },
|
|
264
|
+
inputSize: INPUT_SIZE,
|
|
265
|
+
labels: SCRYPTED_LABEL_DEFS,
|
|
266
|
+
preprocessMode: "resize",
|
|
267
|
+
classMap: SCRYPTED_IDENTITY_CLASS_MAP,
|
|
268
|
+
group
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function freezeEntry(spec) {
|
|
272
|
+
const files = spec.files.map((file) => Object.freeze({
|
|
273
|
+
relativePath: assertScryptedRelativePath(file.relativePath),
|
|
274
|
+
size: file.size,
|
|
275
|
+
sha256: assertScryptedFileSha256(file.sha256, file.relativePath)
|
|
276
|
+
}));
|
|
277
|
+
return Object.freeze({
|
|
278
|
+
id: spec.id,
|
|
279
|
+
label: spec.label,
|
|
280
|
+
family: spec.family,
|
|
281
|
+
variant: spec.variant,
|
|
282
|
+
format: spec.format,
|
|
283
|
+
precision: spec.precision,
|
|
284
|
+
inputSize: INPUT_SIZE,
|
|
285
|
+
files: Object.freeze(files),
|
|
286
|
+
model: buildModel({
|
|
287
|
+
...spec,
|
|
288
|
+
files
|
|
289
|
+
})
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
var SCRYPTED_YOLO_MANIFEST = Object.freeze(scrypted_yolo_manifest_data_default.map(freezeEntry));
|
|
293
|
+
function getScryptedManifestEntry(manifestId) {
|
|
294
|
+
return SCRYPTED_YOLO_MANIFEST.find((entry) => entry.id === manifestId);
|
|
295
|
+
}
|
|
296
|
+
//#endregion
|
|
297
|
+
//#region src/scrypted/scrypted-tree-drift.ts
|
|
298
|
+
/**
|
|
299
|
+
* Read-only drift check: compare the local Scrypted YOLO manifest to a
|
|
300
|
+
* Hugging Face tree. Never mutates the manifest and never auto-imports
|
|
301
|
+
* remote-only files.
|
|
302
|
+
*/
|
|
303
|
+
var SCRYPTED_KNOWN_DRIFT_STATUSES = [
|
|
304
|
+
"ok",
|
|
305
|
+
"missing",
|
|
306
|
+
"size-changed",
|
|
307
|
+
"unavailable"
|
|
308
|
+
];
|
|
309
|
+
/** Fail-closed: a future/unknown status is never treated as `ok`. */
|
|
310
|
+
function normalizeScryptedDrift(row) {
|
|
311
|
+
if (SCRYPTED_KNOWN_DRIFT_STATUSES.includes(row.status)) return {
|
|
312
|
+
manifestId: row.manifestId,
|
|
313
|
+
status: row.status,
|
|
314
|
+
...row.details !== void 0 ? { details: row.details } : {}
|
|
315
|
+
};
|
|
316
|
+
return {
|
|
317
|
+
manifestId: row.manifestId,
|
|
318
|
+
status: "unavailable",
|
|
319
|
+
details: row.details ?? `unknown drift status: ${row.status}`
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function remoteSize(file) {
|
|
323
|
+
return file.lfs?.size ?? file.size;
|
|
324
|
+
}
|
|
325
|
+
function compareScryptedManifestDrift(manifest, tree) {
|
|
326
|
+
const byPath = new Map(tree.map((file) => [file.path, file]));
|
|
327
|
+
return manifest.map((entry) => {
|
|
328
|
+
const missing = [];
|
|
329
|
+
const changed = [];
|
|
330
|
+
for (const file of entry.files) {
|
|
331
|
+
const remote = byPath.get(file.relativePath);
|
|
332
|
+
if (!remote) {
|
|
333
|
+
missing.push(file.relativePath);
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (remoteSize(remote) !== file.size) changed.push(`${file.relativePath} declared ${file.size} remote ${remoteSize(remote)}`);
|
|
337
|
+
}
|
|
338
|
+
if (missing.length > 0) return {
|
|
339
|
+
manifestId: entry.id,
|
|
340
|
+
status: "missing",
|
|
341
|
+
details: `missing: ${missing.join(", ")}`
|
|
342
|
+
};
|
|
343
|
+
if (changed.length > 0) return {
|
|
344
|
+
manifestId: entry.id,
|
|
345
|
+
status: "size-changed",
|
|
346
|
+
details: `size-changed: ${changed.join(", ")}`
|
|
347
|
+
};
|
|
348
|
+
return {
|
|
349
|
+
manifestId: entry.id,
|
|
350
|
+
status: "ok"
|
|
351
|
+
};
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
var ScryptedTreeLinkError = class extends Error {
|
|
355
|
+
constructor(message) {
|
|
356
|
+
super(message);
|
|
357
|
+
this.name = "ScryptedTreeLinkError";
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
function assertScryptedTreePageUrl(url, revision) {
|
|
361
|
+
if (url.protocol !== "https:") throw new ScryptedTreeLinkError(`Refusing non-https tree link: ${url.protocol}`);
|
|
362
|
+
if (url.username || url.password) throw new ScryptedTreeLinkError("Refusing tree link with userinfo");
|
|
363
|
+
if (url.origin !== `https://huggingface.co`) throw new ScryptedTreeLinkError(`Refusing tree link origin ${url.origin}`);
|
|
364
|
+
const expectedPath = `/api/models/${SCRYPTED_HF_REPO}/tree/${revision}`;
|
|
365
|
+
if (url.pathname !== expectedPath) throw new ScryptedTreeLinkError(`Refusing tree link path/revision ${url.pathname} (expected ${expectedPath})`);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Parse `Link: <...>; rel="next"` and accept only an HTTPS huggingface.co
|
|
369
|
+
* tree URL for the same repo + revision. Relative hrefs resolve against the
|
|
370
|
+
* current page URL. Never returns a URL that failed validation.
|
|
371
|
+
*/
|
|
372
|
+
function resolveScryptedTreeNextLink(header, currentUrl, revision) {
|
|
373
|
+
if (!header) return void 0;
|
|
374
|
+
const match = /<([^>]+)>;\s*rel="next"/.exec(header);
|
|
375
|
+
if (!match?.[1]) return void 0;
|
|
376
|
+
let resolved;
|
|
377
|
+
try {
|
|
378
|
+
resolved = new URL(match[1], currentUrl);
|
|
379
|
+
} catch {
|
|
380
|
+
throw new ScryptedTreeLinkError(`Refusing unparsable tree next link: ${match[1]}`);
|
|
381
|
+
}
|
|
382
|
+
assertScryptedTreePageUrl(resolved, revision);
|
|
383
|
+
return resolved.href;
|
|
384
|
+
}
|
|
385
|
+
async function sleep(ms) {
|
|
386
|
+
if (ms <= 0) return;
|
|
387
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
388
|
+
}
|
|
389
|
+
async function fetchScryptedRemoteTree(opts) {
|
|
390
|
+
const revision = opts.revision ?? "main";
|
|
391
|
+
const maxRetries = opts.maxRetries ?? 4;
|
|
392
|
+
const retryDelayMs = opts.retryDelayMs ?? 200;
|
|
393
|
+
let lastError = /* @__PURE__ */ new Error("Scrypted Hugging Face tree unavailable");
|
|
394
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) try {
|
|
395
|
+
const files = [];
|
|
396
|
+
let url = `${SCRYPTED_HF_HOST}/api/models/${SCRYPTED_HF_REPO}/tree/${encodeURIComponent(revision)}?recursive=true&expand=true`;
|
|
397
|
+
let pages = 0;
|
|
398
|
+
while (url) {
|
|
399
|
+
pages += 1;
|
|
400
|
+
if (pages > 50) throw new Error("Scrypted Hugging Face tree pagination exceeded the bound");
|
|
401
|
+
assertScryptedTreePageUrl(new URL(url), revision);
|
|
402
|
+
const response = await opts.fetchImpl(url, {
|
|
403
|
+
redirect: "manual",
|
|
404
|
+
headers: { "User-Agent": "CamStack/1.0" }
|
|
405
|
+
});
|
|
406
|
+
if (response.status === 301 || response.status === 302 || response.status === 303 || response.status === 307 || response.status === 308) throw new ScryptedTreeLinkError(`Refusing tree API redirect ${response.status} (fail-closed, no follow)`);
|
|
407
|
+
if (!response.ok) throw new Error(`HTTP ${response.status} fetching Scrypted Hugging Face tree`);
|
|
408
|
+
const payload = await response.json();
|
|
409
|
+
for (const entry of payload) {
|
|
410
|
+
if (entry.type !== "file" || !entry.path) continue;
|
|
411
|
+
files.push({
|
|
412
|
+
path: entry.path,
|
|
413
|
+
size: entry.size ?? entry.lfs?.size ?? 0,
|
|
414
|
+
...entry.lfs !== void 0 ? { lfs: entry.lfs } : {}
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
url = resolveScryptedTreeNextLink(response.headers.get("link") ?? "", url, revision);
|
|
418
|
+
}
|
|
419
|
+
return files;
|
|
420
|
+
} catch (err) {
|
|
421
|
+
if (err instanceof ScryptedTreeLinkError) throw err;
|
|
422
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
423
|
+
await sleep(Math.min(retryDelayMs * 2 ** attempt, 3e3));
|
|
424
|
+
}
|
|
425
|
+
throw new Error(`Scrypted Hugging Face tree unavailable: ${lastError.message}`);
|
|
426
|
+
}
|
|
427
|
+
//#endregion
|
|
428
|
+
//#region src/scrypted/scrypted-import.ts
|
|
429
|
+
/**
|
|
430
|
+
* Server-owned Scrypted catalog import. The only client input is `manifestId`.
|
|
431
|
+
* Downloads use the pinned host/repo/revision, a bounded staging dir, LFS /
|
|
432
|
+
* size / checksum checks, and atomic finalize with cleanup on every failure.
|
|
433
|
+
*/
|
|
434
|
+
var SCRYPTED_IMPORT_MAX_FILE_BYTES = 256 * 1024 * 1024;
|
|
435
|
+
/** Deterministic bundle digest: sorted `path\\0sha256` lines, never the primary file alone. */
|
|
436
|
+
function scryptedBundleFingerprint(entry) {
|
|
437
|
+
const hash = (0, node_crypto.createHash)("sha256");
|
|
438
|
+
const lines = entry.files.map((file) => `${file.relativePath}\0${file.sha256}`).toSorted();
|
|
439
|
+
hash.update(lines.join("\n"));
|
|
440
|
+
return hash.digest("hex");
|
|
441
|
+
}
|
|
442
|
+
var LFS_POINTER_PREFIX = "version https://git-lfs.github.com/spec/v1";
|
|
443
|
+
var SIZE_TOLERANCE = .01;
|
|
444
|
+
function isLfsPointer(filePath) {
|
|
445
|
+
const fd = node_fs.openSync(filePath, "r");
|
|
446
|
+
try {
|
|
447
|
+
const buf = Buffer.alloc(128);
|
|
448
|
+
const n = node_fs.readSync(fd, buf, 0, buf.length, 0);
|
|
449
|
+
return buf.subarray(0, n).toString("utf8").startsWith(LFS_POINTER_PREFIX);
|
|
450
|
+
} finally {
|
|
451
|
+
node_fs.closeSync(fd);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
async function hashFileSha256(filePath) {
|
|
455
|
+
const hash = (0, node_crypto.createHash)("sha256");
|
|
456
|
+
await new Promise((resolve, reject) => {
|
|
457
|
+
node_fs.createReadStream(filePath).on("data", (chunk) => hash.update(chunk)).on("end", () => resolve()).on("error", reject);
|
|
458
|
+
});
|
|
459
|
+
return hash.digest("hex");
|
|
460
|
+
}
|
|
461
|
+
function assertInside(root, dest) {
|
|
462
|
+
const rootResolved = node_path.resolve(root);
|
|
463
|
+
const destResolved = node_path.resolve(dest);
|
|
464
|
+
const prefix = rootResolved.endsWith(node_path.sep) ? rootResolved : `${rootResolved}${node_path.sep}`;
|
|
465
|
+
if (destResolved !== rootResolved && !destResolved.startsWith(prefix)) throw new Error(`Refusing to write outside the destination root: ${dest}`);
|
|
466
|
+
return destResolved;
|
|
467
|
+
}
|
|
468
|
+
function rejectSymlink(filePath) {
|
|
469
|
+
try {
|
|
470
|
+
if (node_fs.lstatSync(filePath).isSymbolicLink()) throw new Error(`Refusing symlink at ${filePath}`);
|
|
471
|
+
} catch (err) {
|
|
472
|
+
if (err.code === "ENOENT") return;
|
|
473
|
+
throw err;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function sizeOutOfTolerance(actual, declared) {
|
|
477
|
+
if (declared <= 0) return true;
|
|
478
|
+
return Math.abs(actual - declared) / declared > SIZE_TOLERANCE;
|
|
479
|
+
}
|
|
480
|
+
function localBasename(entry) {
|
|
481
|
+
if (entry.format === "coreml") return (entry.files.find((file) => file.relativePath.endsWith("/Manifest.json"))?.relativePath.replace(/\/Manifest\.json$/, "") ?? `${entry.id}.mlpackage`).split("/").pop() ?? `${entry.id}.mlpackage`;
|
|
482
|
+
if (entry.format === "openvino") return `${entry.id}.xml`;
|
|
483
|
+
if (entry.format === "tflite") return `${entry.id}.tflite`;
|
|
484
|
+
return entry.files[0]?.relativePath.split("/").pop() ?? `${entry.id}.onnx`;
|
|
485
|
+
}
|
|
486
|
+
function buildImportedDescriptor(entry, localName) {
|
|
487
|
+
const catalog = {
|
|
488
|
+
...entry.model,
|
|
489
|
+
classMap: SCRYPTED_IDENTITY_CLASS_MAP,
|
|
490
|
+
formats: { ...entry.model.formats }
|
|
491
|
+
};
|
|
492
|
+
const format = entry.format;
|
|
493
|
+
const previous = catalog.formats[format];
|
|
494
|
+
if (format === "coreml") catalog.formats = {
|
|
495
|
+
...catalog.formats,
|
|
496
|
+
coreml: {
|
|
497
|
+
url: `camstack-local://${entry.id}/${localName}`,
|
|
498
|
+
sizeMB: previous?.sizeMB ?? entry.files.reduce((sum, file) => sum + file.size, 0) / 1e6,
|
|
499
|
+
isDirectory: true,
|
|
500
|
+
files: previous?.files ?? [
|
|
501
|
+
"Manifest.json",
|
|
502
|
+
"Data/com.apple.CoreML/model.mlmodel",
|
|
503
|
+
"Data/com.apple.CoreML/weights/weight.bin"
|
|
504
|
+
],
|
|
505
|
+
runtimes: ["python"]
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
else if (format === "openvino") catalog.formats = {
|
|
509
|
+
...catalog.formats,
|
|
510
|
+
openvino: {
|
|
511
|
+
url: `camstack-local://${entry.id}/${localName}`,
|
|
512
|
+
sizeMB: previous?.sizeMB ?? entry.files.reduce((sum, file) => sum + file.size, 0) / 1e6,
|
|
513
|
+
files: [`${entry.id}.bin`],
|
|
514
|
+
runtimes: ["python"]
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
else catalog.formats = {
|
|
518
|
+
...catalog.formats,
|
|
519
|
+
[format]: {
|
|
520
|
+
url: `camstack-local://${entry.id}/${localName}`,
|
|
521
|
+
sizeMB: previous?.sizeMB ?? entry.files.reduce((sum, file) => sum + file.size, 0) / 1e6,
|
|
522
|
+
...previous?.runtimes !== void 0 ? { runtimes: previous.runtimes } : {}
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
return {
|
|
526
|
+
stepId: "object-detection",
|
|
527
|
+
entry: catalog
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
/** Availability/format selection uses the manifesto format, never key order. */
|
|
531
|
+
function resolveScryptedImportedFormat(manifestId, descriptor) {
|
|
532
|
+
const entry = getScryptedManifestEntry(manifestId);
|
|
533
|
+
if (!entry) throw new Error(`resolveScryptedImportedFormat: unknown manifest id "${manifestId}"`);
|
|
534
|
+
const format = entry.format;
|
|
535
|
+
if (!descriptor.entry.formats[format]) throw new Error(`resolveScryptedImportedFormat: descriptor for "${manifestId}" is missing manifesto format ${format}`);
|
|
536
|
+
return format;
|
|
537
|
+
}
|
|
538
|
+
async function verifyScryptedStagedFile(destPath, file, deps) {
|
|
539
|
+
const maxBytes = deps.maxFileBytes ?? 268435456;
|
|
540
|
+
if (file.size > maxBytes) throw new Error(`Declared size ${file.size} exceeds the ${maxBytes}-byte limit`);
|
|
541
|
+
if (!node_fs.existsSync(destPath)) throw new Error(`missing downloaded file for ${file.relativePath}`);
|
|
542
|
+
rejectSymlink(destPath);
|
|
543
|
+
const actual = node_fs.statSync(destPath).size;
|
|
544
|
+
if (actual > maxBytes) throw new Error(`Downloaded ${actual} bytes exceeds the ${maxBytes}-byte limit`);
|
|
545
|
+
if (isLfsPointer(destPath)) throw new Error(`Downloaded an LFS pointer instead of the artifact (${file.relativePath})`);
|
|
546
|
+
if (sizeOutOfTolerance(actual, file.size)) throw new Error(`Downloaded size ${actual} is outside tolerance of declared ${file.size} for ${file.relativePath}`);
|
|
547
|
+
const expected = assertScryptedFileSha256(file.sha256, file.relativePath);
|
|
548
|
+
if (await (deps.fileSha256 ?? hashFileSha256)(destPath) !== expected) throw new Error(`sha256 checksum mismatch for ${file.relativePath}`);
|
|
549
|
+
}
|
|
550
|
+
async function importScryptedCatalogModel(input, deps) {
|
|
551
|
+
const entry = (deps.resolveEntry ?? getScryptedManifestEntry)(input.manifestId);
|
|
552
|
+
if (!entry) throw new Error(`importScryptedCatalogModel: unknown manifest id "${input.manifestId}"`);
|
|
553
|
+
for (const file of entry.files) assertScryptedFileSha256(file.sha256, file.relativePath);
|
|
554
|
+
const drift = normalizeScryptedDrift(await deps.driftFor(entry.id));
|
|
555
|
+
if (drift.status !== "ok") throw new Error(`importScryptedCatalogModel: entry "${entry.id}" is blocked by drift (${drift.status}${drift.details ? `: ${drift.details}` : ""})`);
|
|
556
|
+
node_fs.mkdirSync(deps.stagingDir, { recursive: true });
|
|
557
|
+
node_fs.mkdirSync(deps.modelsDir, { recursive: true });
|
|
558
|
+
const stagingRoot = node_path.join(deps.stagingDir, `imp-${(0, node_crypto.randomUUID)()}`);
|
|
559
|
+
node_fs.mkdirSync(stagingRoot, { recursive: true });
|
|
560
|
+
try {
|
|
561
|
+
const staged = [];
|
|
562
|
+
for (const file of entry.files) {
|
|
563
|
+
const relative = assertScryptedRelativePath(file.relativePath);
|
|
564
|
+
const dest = assertInside(stagingRoot, node_path.join(stagingRoot, ...relative.split("/")));
|
|
565
|
+
const url = buildScryptedResolveUrl(relative);
|
|
566
|
+
await deps.downloadFile(url, dest);
|
|
567
|
+
await verifyScryptedStagedFile(dest, file, deps);
|
|
568
|
+
staged.push({
|
|
569
|
+
file,
|
|
570
|
+
dest
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
if (entry.format === "coreml") {
|
|
574
|
+
const hasManifest = staged.some((row) => row.file.relativePath.endsWith("Manifest.json"));
|
|
575
|
+
const hasModel = staged.some((row) => row.file.relativePath.endsWith("model.mlmodel"));
|
|
576
|
+
const hasWeight = staged.some((row) => row.file.relativePath.endsWith("weight.bin"));
|
|
577
|
+
if (!hasManifest || !hasModel || !hasWeight || staged.length < 3) throw new Error("CoreML payload is incomplete — Manifest.json, model and weights are required");
|
|
578
|
+
}
|
|
579
|
+
if (entry.format === "openvino") {
|
|
580
|
+
const hasXml = staged.some((row) => row.file.relativePath.endsWith(".xml"));
|
|
581
|
+
const hasBin = staged.some((row) => row.file.relativePath.endsWith(".bin"));
|
|
582
|
+
if (!hasXml || !hasBin) throw new Error("OpenVINO payload is incomplete — xml and bin siblings are required");
|
|
583
|
+
}
|
|
584
|
+
const localName = localBasename(entry);
|
|
585
|
+
const finalMain = assertInside(deps.modelsDir, node_path.join(deps.modelsDir, localName));
|
|
586
|
+
rejectSymlink(finalMain);
|
|
587
|
+
if (entry.format === "coreml") {
|
|
588
|
+
const pkgRel = staged.find((row) => row.file.relativePath.endsWith("Manifest.json"))?.file.relativePath.replace(/\/Manifest\.json$/, "");
|
|
589
|
+
if (!pkgRel) throw new Error("CoreML payload is incomplete — Manifest.json missing");
|
|
590
|
+
const stagedPkg = node_path.join(stagingRoot, ...pkgRel.split("/"));
|
|
591
|
+
node_fs.rmSync(finalMain, {
|
|
592
|
+
recursive: true,
|
|
593
|
+
force: true
|
|
594
|
+
});
|
|
595
|
+
node_fs.renameSync(stagedPkg, finalMain);
|
|
596
|
+
} else if (entry.format === "openvino") {
|
|
597
|
+
const xml = staged.find((row) => row.file.relativePath.endsWith(".xml"));
|
|
598
|
+
const bin = staged.find((row) => row.file.relativePath.endsWith(".bin"));
|
|
599
|
+
if (!xml || !bin) throw new Error("OpenVINO payload is incomplete");
|
|
600
|
+
const xmlDest = finalMain;
|
|
601
|
+
const binDest = assertInside(deps.modelsDir, node_path.join(deps.modelsDir, `${entry.id}.bin`));
|
|
602
|
+
node_fs.renameSync(xml.dest, xmlDest);
|
|
603
|
+
node_fs.renameSync(bin.dest, binDest);
|
|
604
|
+
} else {
|
|
605
|
+
const only = staged[0];
|
|
606
|
+
if (!only) throw new Error("payload is incomplete");
|
|
607
|
+
node_fs.renameSync(only.dest, finalMain);
|
|
608
|
+
}
|
|
609
|
+
return buildImportedDescriptor(entry, localName);
|
|
610
|
+
} finally {
|
|
611
|
+
node_fs.rmSync(stagingRoot, {
|
|
612
|
+
recursive: true,
|
|
613
|
+
force: true
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
//#endregion
|
|
93
618
|
//#region ../types/dist/event-category-CIa_iT6b.mjs
|
|
94
619
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
95
620
|
EventCategory["SystemBoot"] = "system.boot";
|
|
@@ -8154,6 +8679,15 @@ var LabelDefinitionSchema = object({
|
|
|
8154
8679
|
description: string().optional(),
|
|
8155
8680
|
icon: string().optional()
|
|
8156
8681
|
});
|
|
8682
|
+
var ClassMapDefinitionSchema = object({
|
|
8683
|
+
mapping: record(string(), _enum([
|
|
8684
|
+
"person",
|
|
8685
|
+
"vehicle",
|
|
8686
|
+
"animal",
|
|
8687
|
+
"package"
|
|
8688
|
+
])),
|
|
8689
|
+
preserveOriginal: boolean()
|
|
8690
|
+
});
|
|
8157
8691
|
var MODEL_FORMATS = [
|
|
8158
8692
|
"onnx",
|
|
8159
8693
|
"coreml",
|
|
@@ -8332,7 +8866,13 @@ var ModelCatalogEntrySchema = object({
|
|
|
8332
8866
|
* `id` stays the source of truth for resolution/download/persistence; grouping
|
|
8333
8867
|
* is a presentation overlay resolved back to an `id`.
|
|
8334
8868
|
*/
|
|
8335
|
-
group: ModelVariantGroupSchema.optional()
|
|
8869
|
+
group: ModelVariantGroupSchema.optional(),
|
|
8870
|
+
/**
|
|
8871
|
+
* Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
|
|
8872
|
+
* applies (Frigate / COCO public catalog). Set on a custom model whose raw
|
|
8873
|
+
* labels already ARE the CamStack macros (Scrypted identity map).
|
|
8874
|
+
*/
|
|
8875
|
+
classMap: ClassMapDefinitionSchema.optional()
|
|
8336
8876
|
});
|
|
8337
8877
|
var ConvertTargetSchema = discriminatedUnion("format", [object({
|
|
8338
8878
|
format: literal("openvino"),
|
|
@@ -8361,7 +8901,8 @@ var ModelConvertMetadataSchema = object({
|
|
|
8361
8901
|
"ocr",
|
|
8362
8902
|
"segmentation"
|
|
8363
8903
|
]),
|
|
8364
|
-
faceAlignment: boolean().optional()
|
|
8904
|
+
faceAlignment: boolean().optional(),
|
|
8905
|
+
classMap: ClassMapDefinitionSchema.optional()
|
|
8365
8906
|
});
|
|
8366
8907
|
var ConvertArtifactSchema = object({
|
|
8367
8908
|
format: _enum(MODEL_FORMATS),
|
|
@@ -17393,6 +17934,33 @@ var NativeCropRefSchema = object({
|
|
|
17393
17934
|
h: number()
|
|
17394
17935
|
})
|
|
17395
17936
|
});
|
|
17937
|
+
object({
|
|
17938
|
+
crop: object({
|
|
17939
|
+
left: number(),
|
|
17940
|
+
top: number(),
|
|
17941
|
+
width: number().positive(),
|
|
17942
|
+
height: number().positive()
|
|
17943
|
+
}).optional(),
|
|
17944
|
+
content: object({
|
|
17945
|
+
width: number().int().positive(),
|
|
17946
|
+
height: number().int().positive()
|
|
17947
|
+
}),
|
|
17948
|
+
fit: _enum(["stretch", "contain"]),
|
|
17949
|
+
format: _enum([
|
|
17950
|
+
"rgb",
|
|
17951
|
+
"gray",
|
|
17952
|
+
"jpeg"
|
|
17953
|
+
])
|
|
17954
|
+
});
|
|
17955
|
+
var FrameRefSchema = object({
|
|
17956
|
+
registryId: string().min(1),
|
|
17957
|
+
id: string().min(1),
|
|
17958
|
+
width: number().int().positive(),
|
|
17959
|
+
height: number().int().positive(),
|
|
17960
|
+
format: _enum(["rgb", "gray"]),
|
|
17961
|
+
timestamp: number(),
|
|
17962
|
+
capturedAt: number().optional()
|
|
17963
|
+
});
|
|
17396
17964
|
var ModelFormatSchema$1 = _enum([
|
|
17397
17965
|
"onnx",
|
|
17398
17966
|
"coreml",
|
|
@@ -17637,6 +18205,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
|
|
|
17637
18205
|
steps: array(PipelineStepInputSchema).min(1),
|
|
17638
18206
|
frame: FrameInputSchema.optional(),
|
|
17639
18207
|
/**
|
|
18208
|
+
* Process-local lazy frame. Valid only when caller and provider resolve
|
|
18209
|
+
* in the same execution-group process; split/cross-node callers use
|
|
18210
|
+
* `frame`/`image` inline compatibility instead.
|
|
18211
|
+
*/
|
|
18212
|
+
frameRef: FrameRefSchema.optional(),
|
|
18213
|
+
/**
|
|
17640
18214
|
* CB5 shm passthrough — a `FrameHandle` naming the same ring slot
|
|
17641
18215
|
* the decoded pixels live in. One more member of the one-of
|
|
17642
18216
|
* frame/frameHandle/image/imageBase64/referenceImage group.
|
|
@@ -17892,7 +18466,10 @@ var NativeCropResultSchema = object({
|
|
|
17892
18466
|
* Which source served this crop, so a quality-sensitive consumer (the native
|
|
17893
18467
|
* `keyFrame`) can reject a degraded fallback:
|
|
17894
18468
|
* - `native` — cut from the decode worker's retained NATIVE surface (the
|
|
17895
|
-
* quality path).
|
|
18469
|
+
* quality path). A subject-tile serve is also native-resolution and stays
|
|
18470
|
+
* `native` here: the public enum cannot name `tile` without a breaking cap
|
|
18471
|
+
* change. Runner telemetry distinguishes lease vs tile via `source` on the
|
|
18472
|
+
* internal crop result (`nativeHits` vs `tileHits`).
|
|
17896
18473
|
* - `ram-fullframe` — the native surface MISSED but the request was full-frame,
|
|
17897
18474
|
* so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
|
|
17898
18475
|
* the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
|
|
@@ -18383,12 +18960,41 @@ var RunnerLocalLoadSchema = object({
|
|
|
18383
18960
|
* legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
|
|
18384
18961
|
* working unchanged when they switch to reading from the runner cap.
|
|
18385
18962
|
*/
|
|
18963
|
+
var FrameLazyCountersSchema = object({
|
|
18964
|
+
framesDecoded: number(),
|
|
18965
|
+
framesAdmitted: number(),
|
|
18966
|
+
framesDroppedPixelFree: number(),
|
|
18967
|
+
viewsMaterialized: number(),
|
|
18968
|
+
viewsSkipped: number(),
|
|
18969
|
+
workerToRunnerBytes: number(),
|
|
18970
|
+
runnerToPoolRawBytes: number(),
|
|
18971
|
+
runnerToPoolJpegBytes: number(),
|
|
18972
|
+
onDemandFullFrameRequests: number(),
|
|
18973
|
+
onDemandCropRequests: number(),
|
|
18974
|
+
nativeHits: number(),
|
|
18975
|
+
nativeMisses: number(),
|
|
18976
|
+
tileHits: number(),
|
|
18977
|
+
tileMisses: number(),
|
|
18978
|
+
fallbackHits: number(),
|
|
18979
|
+
fallbackMisses: number(),
|
|
18980
|
+
retainedWritesAvoided: number(),
|
|
18981
|
+
residentRefs: number(),
|
|
18982
|
+
residentBytes: number(),
|
|
18983
|
+
releases: number(),
|
|
18984
|
+
evictions: number(),
|
|
18985
|
+
staleMisses: number()
|
|
18986
|
+
});
|
|
18987
|
+
var FrameLazyMetricsSchema = object({
|
|
18988
|
+
node: FrameLazyCountersSchema,
|
|
18989
|
+
cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
|
|
18990
|
+
});
|
|
18386
18991
|
var RunnerLocalMetricsSchema = object({
|
|
18387
18992
|
nodeId: string(),
|
|
18388
18993
|
activeCameras: number(),
|
|
18389
18994
|
throttledCameras: number(),
|
|
18390
18995
|
avgInferenceTimeMs: number(),
|
|
18391
|
-
queueDepth: number()
|
|
18996
|
+
queueDepth: number(),
|
|
18997
|
+
frameLazy: FrameLazyMetricsSchema.optional()
|
|
18392
18998
|
});
|
|
18393
18999
|
method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mutation" }), method(object({ deviceId: number() }), object({ success: literal(true) }), { kind: "mutation" }), method(ReportMotionInputSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), RunnerLocalLoadSchema), method(_void(), RunnerLocalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(_void(), array(CameraMetricsWithDeviceIdSchema).readonly()), method(_void(), array(number()).readonly()), method(object({
|
|
18394
19000
|
handle: FrameHandleSchema,
|
|
@@ -19688,6 +20294,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
|
|
|
19688
20294
|
location: StorageLocationSchema,
|
|
19689
20295
|
relativePath: string()
|
|
19690
20296
|
}), _void(), { kind: "mutation" }), method(object({ location: StorageLocationSchema }), number().nullable()), method(BeginUploadInputSchema, BeginUploadResultSchema, { kind: "mutation" }), method(WriteChunkInputSchema, _void(), { kind: "mutation" }), method(FinalizeUploadInputSchema, _void(), { kind: "mutation" }), method(AbortUploadInputSchema, _void(), { kind: "mutation" }), method(BeginDownloadInputSchema, BeginDownloadResultSchema, { kind: "mutation" }), method(ReadChunkInputSchema, _instanceof(Uint8Array)), method(EndDownloadInputSchema, _void(), { kind: "mutation" });
|
|
20297
|
+
/** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
|
|
20298
|
+
var ProfileSettingsSchemaBridge = unknown().nullable();
|
|
20299
|
+
var ProfileSettingsBagSchema = record(string(), unknown());
|
|
19691
20300
|
/**
|
|
19692
20301
|
* A live terminal session hosted by the provider addon. Output and input do
|
|
19693
20302
|
* NOT flow through the capability — they use the addon data plane
|
|
@@ -19717,7 +20326,14 @@ var TerminalSessionInfoSchema = object({
|
|
|
19717
20326
|
var TerminalProfileInfoSchema = object({
|
|
19718
20327
|
profileId: string(),
|
|
19719
20328
|
label: string(),
|
|
19720
|
-
description: string().optional()
|
|
20329
|
+
description: string().optional(),
|
|
20330
|
+
/** Spawn defaults the instance form copies on create. */
|
|
20331
|
+
executable: string().optional(),
|
|
20332
|
+
args: array(string()).readonly().optional(),
|
|
20333
|
+
cwd: string().optional(),
|
|
20334
|
+
environment: array(string()).readonly().optional(),
|
|
20335
|
+
/** ConfigUISchema for instance knobs, or null when the profile has none. */
|
|
20336
|
+
settingsSchema: ProfileSettingsSchemaBridge.optional()
|
|
19721
20337
|
});
|
|
19722
20338
|
/**
|
|
19723
20339
|
* A durable operator-created Terminal instance. Profiles are templates; only
|
|
@@ -19730,7 +20346,12 @@ var TerminalInstanceInfoSchema = object({
|
|
|
19730
20346
|
profileId: string(),
|
|
19731
20347
|
profileLabel: string(),
|
|
19732
20348
|
name: string(),
|
|
19733
|
-
enabled: boolean()
|
|
20349
|
+
enabled: boolean(),
|
|
20350
|
+
executable: string(),
|
|
20351
|
+
args: array(string()).readonly(),
|
|
20352
|
+
cwd: string(),
|
|
20353
|
+
environment: array(string()).readonly(),
|
|
20354
|
+
profileSettings: ProfileSettingsBagSchema
|
|
19734
20355
|
});
|
|
19735
20356
|
var TerminalLegacyCameraSchema = object({
|
|
19736
20357
|
stableId: string(),
|
|
@@ -19760,7 +20381,23 @@ var TerminalOutputBatchSchema = object({
|
|
|
19760
20381
|
method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
|
|
19761
20382
|
targetNodeId: string().min(1),
|
|
19762
20383
|
profileId: string().min(1),
|
|
19763
|
-
name: string().trim().min(1).max(160).optional()
|
|
20384
|
+
name: string().trim().min(1).max(160).optional(),
|
|
20385
|
+
executable: string().max(1024).optional(),
|
|
20386
|
+
args: array(string().max(2048)).max(64).optional(),
|
|
20387
|
+
cwd: string().max(1024).optional(),
|
|
20388
|
+
environment: array(string().max(4096)).max(64).optional(),
|
|
20389
|
+
profileSettings: ProfileSettingsBagSchema.optional()
|
|
20390
|
+
}), TerminalInstanceInfoSchema, {
|
|
20391
|
+
kind: "mutation",
|
|
20392
|
+
auth: "admin"
|
|
20393
|
+
}), method(object({
|
|
20394
|
+
instanceId: string().min(1),
|
|
20395
|
+
name: string().trim().min(1).max(160).optional(),
|
|
20396
|
+
executable: string().max(1024).optional(),
|
|
20397
|
+
args: array(string().max(2048)).max(64).optional(),
|
|
20398
|
+
cwd: string().max(1024).optional(),
|
|
20399
|
+
environment: array(string().max(4096)).max(64).optional(),
|
|
20400
|
+
profileSettings: ProfileSettingsBagSchema.optional()
|
|
19764
20401
|
}), TerminalInstanceInfoSchema, {
|
|
19765
20402
|
kind: "mutation",
|
|
19766
20403
|
auth: "admin"
|
|
@@ -19782,7 +20419,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
|
|
|
19782
20419
|
}), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
|
|
19783
20420
|
profileId: string(),
|
|
19784
20421
|
cols: number().int().positive(),
|
|
19785
|
-
rows: number().int().positive()
|
|
20422
|
+
rows: number().int().positive(),
|
|
20423
|
+
executable: string().max(1024).optional(),
|
|
20424
|
+
args: array(string().max(2048)).max(64).optional(),
|
|
20425
|
+
cwd: string().max(1024).optional(),
|
|
20426
|
+
environment: array(string().max(4096)).max(64).optional()
|
|
19786
20427
|
}), TerminalSessionInfoSchema, {
|
|
19787
20428
|
kind: "mutation",
|
|
19788
20429
|
auth: "admin"
|
|
@@ -22564,10 +23205,10 @@ DeviceType.LawnMower, method(object({ deviceId: number().int().nonnegative() }),
|
|
|
22564
23205
|
*
|
|
22565
23206
|
* • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
|
|
22566
23207
|
* to receive an ordered list of candidate base URLs it should race
|
|
22567
|
-
* on connect — LAN IPv4 first (lowest latency
|
|
22568
|
-
* then public hostname (if a tunnel is
|
|
22569
|
-
* race them with short timeouts and stick with the
|
|
22570
|
-
* session.
|
|
23208
|
+
* on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
|
|
23209
|
+
* when on the same network), then public hostname (if a tunnel is
|
|
23210
|
+
* up). The SDK can race them with short timeouts and stick with the
|
|
23211
|
+
* winner for the session.
|
|
22571
23212
|
*
|
|
22572
23213
|
* Why hub-only: agents are not directly addressable by the operator's
|
|
22573
23214
|
* clients — they reverse-connect to the hub. Exposing their interfaces
|
|
@@ -22722,6 +23363,17 @@ var NotificationEndpointSchema = object({
|
|
|
22722
23363
|
/** What the ranking currently resolves to (null when nothing is reachable). */
|
|
22723
23364
|
resolved: string().nullable()
|
|
22724
23365
|
});
|
|
23366
|
+
/**
|
|
23367
|
+
* The URLs the SDK / viewer should race for API access. `baseUrls` empty =
|
|
23368
|
+
* AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
|
|
23369
|
+
* currently expands to, so the UI can show the effective set either way.
|
|
23370
|
+
*/
|
|
23371
|
+
var ViewerEndpointsSchema = object({
|
|
23372
|
+
/** The operator's explicit race set, or empty for AUTO. */
|
|
23373
|
+
baseUrls: array(string()).readonly(),
|
|
23374
|
+
/** What the ranking currently resolves to (may be empty if nothing is up). */
|
|
23375
|
+
resolved: array(string()).readonly()
|
|
23376
|
+
});
|
|
22725
23377
|
var AllowedAddressesSchema = object({
|
|
22726
23378
|
/**
|
|
22727
23379
|
* Allowlist of interface addresses operators have explicitly opted
|
|
@@ -22730,6 +23382,20 @@ var AllowedAddressesSchema = object({
|
|
|
22730
23382
|
* Network Addresses admin page and persisted by the addon.
|
|
22731
23383
|
*/
|
|
22732
23384
|
addresses: array(string()).readonly() });
|
|
23385
|
+
var TlsStatusSchema = object({
|
|
23386
|
+
mode: _enum([
|
|
23387
|
+
"generated",
|
|
23388
|
+
"uploaded",
|
|
23389
|
+
"disabled"
|
|
23390
|
+
]),
|
|
23391
|
+
leafFingerprintSha256: string().nullable(),
|
|
23392
|
+
caFingerprintSha256: string().nullable(),
|
|
23393
|
+
validTo: string().nullable(),
|
|
23394
|
+
sans: array(string()),
|
|
23395
|
+
caCertPem: string().nullable(),
|
|
23396
|
+
reissueError: string().nullable(),
|
|
23397
|
+
restartRequired: boolean()
|
|
23398
|
+
});
|
|
22733
23399
|
method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
|
|
22734
23400
|
/**
|
|
22735
23401
|
* LEGACY HINT — do not send from new code. Kept optional so clients
|
|
@@ -22739,17 +23405,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
|
|
|
22739
23405
|
*/
|
|
22740
23406
|
port: number().int().min(1).max(65535).optional(),
|
|
22741
23407
|
/** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
|
|
22742
|
-
* candidate. Default `
|
|
23408
|
+
* candidate. Default `false` — loopback is not a client route. */
|
|
22743
23409
|
includeLoopback: boolean().optional(),
|
|
22744
|
-
/** Skip IPv6 entries.
|
|
22745
|
-
*
|
|
23410
|
+
/** Skip IPv6 entries. Default `false` — the palette includes stable
|
|
23411
|
+
* LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
|
|
23412
|
+
* hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
|
|
22746
23413
|
ipv4Only: boolean().optional(),
|
|
22747
23414
|
/** Scheme to emit for LAN/loopback URLs. Default `'http'`.
|
|
22748
23415
|
* Pass `'https'` when the caller is itself loaded over HTTPS
|
|
22749
23416
|
* to avoid mixed-content blocks in the browser. The public
|
|
22750
23417
|
* tunnel always emits `https://` regardless. */
|
|
22751
23418
|
scheme: _enum(["http", "https"]).optional()
|
|
22752
|
-
}), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" })
|
|
23419
|
+
}), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), ViewerEndpointsSchema), method(object({ baseUrls: array(string()).readonly() }), ViewerEndpointsSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" }), method(_void(), TlsStatusSchema), method(object({ reason: string().optional() }), TlsStatusSchema, {
|
|
23420
|
+
kind: "mutation",
|
|
23421
|
+
auth: "admin"
|
|
23422
|
+
}), method(object({
|
|
23423
|
+
certPem: string().min(1),
|
|
23424
|
+
keyPem: string().min(1),
|
|
23425
|
+
caPem: string().optional()
|
|
23426
|
+
}), TlsStatusSchema, {
|
|
23427
|
+
kind: "mutation",
|
|
23428
|
+
auth: "admin"
|
|
23429
|
+
}), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
|
|
23430
|
+
kind: "mutation",
|
|
23431
|
+
auth: "admin"
|
|
23432
|
+
});
|
|
22753
23433
|
object({
|
|
22754
23434
|
/** Lifecycle state of the lock. `jammed` means the motor reported
|
|
22755
23435
|
* failure to reach the target — operator intervention required. */
|
|
@@ -28512,6 +29192,12 @@ Object.freeze({
|
|
|
28512
29192
|
addonId: null,
|
|
28513
29193
|
access: "create"
|
|
28514
29194
|
},
|
|
29195
|
+
"localNetwork.downloadCa": {
|
|
29196
|
+
capName: "local-network",
|
|
29197
|
+
capScope: "system",
|
|
29198
|
+
addonId: null,
|
|
29199
|
+
access: "view"
|
|
29200
|
+
},
|
|
28515
29201
|
"localNetwork.getAllowedAddresses": {
|
|
28516
29202
|
capName: "local-network",
|
|
28517
29203
|
capScope: "system",
|
|
@@ -28536,18 +29222,42 @@ Object.freeze({
|
|
|
28536
29222
|
addonId: null,
|
|
28537
29223
|
access: "view"
|
|
28538
29224
|
},
|
|
29225
|
+
"localNetwork.getTlsStatus": {
|
|
29226
|
+
capName: "local-network",
|
|
29227
|
+
capScope: "system",
|
|
29228
|
+
addonId: null,
|
|
29229
|
+
access: "view"
|
|
29230
|
+
},
|
|
29231
|
+
"localNetwork.getViewerEndpoints": {
|
|
29232
|
+
capName: "local-network",
|
|
29233
|
+
capScope: "system",
|
|
29234
|
+
addonId: null,
|
|
29235
|
+
access: "view"
|
|
29236
|
+
},
|
|
28539
29237
|
"localNetwork.list": {
|
|
28540
29238
|
capName: "local-network",
|
|
28541
29239
|
capScope: "system",
|
|
28542
29240
|
addonId: null,
|
|
28543
29241
|
access: "view"
|
|
28544
29242
|
},
|
|
29243
|
+
"localNetwork.regenerateCertificate": {
|
|
29244
|
+
capName: "local-network",
|
|
29245
|
+
capScope: "system",
|
|
29246
|
+
addonId: null,
|
|
29247
|
+
access: "create"
|
|
29248
|
+
},
|
|
28545
29249
|
"localNetwork.resetAllowlistToBestMatch": {
|
|
28546
29250
|
capName: "local-network",
|
|
28547
29251
|
capScope: "system",
|
|
28548
29252
|
addonId: null,
|
|
28549
29253
|
access: "delete"
|
|
28550
29254
|
},
|
|
29255
|
+
"localNetwork.revertToGeneratedCertificate": {
|
|
29256
|
+
capName: "local-network",
|
|
29257
|
+
capScope: "system",
|
|
29258
|
+
addonId: null,
|
|
29259
|
+
access: "create"
|
|
29260
|
+
},
|
|
28551
29261
|
"localNetwork.setAllowedAddresses": {
|
|
28552
29262
|
capName: "local-network",
|
|
28553
29263
|
capScope: "system",
|
|
@@ -28560,6 +29270,18 @@ Object.freeze({
|
|
|
28560
29270
|
addonId: null,
|
|
28561
29271
|
access: "create"
|
|
28562
29272
|
},
|
|
29273
|
+
"localNetwork.setViewerEndpoints": {
|
|
29274
|
+
capName: "local-network",
|
|
29275
|
+
capScope: "system",
|
|
29276
|
+
addonId: null,
|
|
29277
|
+
access: "create"
|
|
29278
|
+
},
|
|
29279
|
+
"localNetwork.uploadCertificate": {
|
|
29280
|
+
capName: "local-network",
|
|
29281
|
+
capScope: "system",
|
|
29282
|
+
addonId: null,
|
|
29283
|
+
access: "create"
|
|
29284
|
+
},
|
|
28563
29285
|
"lockControl.lock": {
|
|
28564
29286
|
capName: "lock-control",
|
|
28565
29287
|
capScope: "device",
|
|
@@ -31440,6 +32162,12 @@ Object.freeze({
|
|
|
31440
32162
|
addonId: null,
|
|
31441
32163
|
access: "create"
|
|
31442
32164
|
},
|
|
32165
|
+
"terminalSession.updateInstance": {
|
|
32166
|
+
capName: "terminal-session",
|
|
32167
|
+
capScope: "system",
|
|
32168
|
+
addonId: null,
|
|
32169
|
+
access: "create"
|
|
32170
|
+
},
|
|
31443
32171
|
"terminalSession.writeInput": {
|
|
31444
32172
|
capName: "terminal-session",
|
|
31445
32173
|
capScope: "system",
|
|
@@ -33927,6 +34655,35 @@ Object.freeze(Object.fromEntries([{
|
|
|
33927
34655
|
}]
|
|
33928
34656
|
}].map((s) => [s.stepId, s.defaultModelId])));
|
|
33929
34657
|
string().min(1);
|
|
34658
|
+
var CLUSTER_STEP_SETTING_FIELDS = [{
|
|
34659
|
+
stepId: "face-embedding",
|
|
34660
|
+
key: "minLandmarkFaceSize",
|
|
34661
|
+
label: "Min face size for recognition (detection px)",
|
|
34662
|
+
description: "Refuse to embed a face smaller than this IN THE DETECTION FRAME. Applies to every node — the enrolled gallery is one index, and two admission floors would fill it from two policies. 0 disables the gate.",
|
|
34663
|
+
type: "slider",
|
|
34664
|
+
min: 0,
|
|
34665
|
+
max: 64,
|
|
34666
|
+
step: 2,
|
|
34667
|
+
default: 24
|
|
34668
|
+
}];
|
|
34669
|
+
function clusterStepSettingKey(stepId, fieldKey) {
|
|
34670
|
+
return `clusterStepSetting:${stepId}:${fieldKey}`;
|
|
34671
|
+
}
|
|
34672
|
+
var ClusterSettingNumberSchema = number().finite();
|
|
34673
|
+
function readClusterStepSettings(config) {
|
|
34674
|
+
const out = {};
|
|
34675
|
+
for (const field of CLUSTER_STEP_SETTING_FIELDS) {
|
|
34676
|
+
const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
|
|
34677
|
+
const value = parsed.success ? parsed.data : field.default;
|
|
34678
|
+
const existing = out[field.stepId] ?? {};
|
|
34679
|
+
out[field.stepId] = {
|
|
34680
|
+
...existing,
|
|
34681
|
+
[field.key]: value
|
|
34682
|
+
};
|
|
34683
|
+
}
|
|
34684
|
+
return out;
|
|
34685
|
+
}
|
|
34686
|
+
readClusterStepSettings({});
|
|
33930
34687
|
object({
|
|
33931
34688
|
/**
|
|
33932
34689
|
* Fraction of the box's own size added on EACH side before cutting.
|
|
@@ -34176,7 +34933,8 @@ function buildEntry(metadata, artifacts) {
|
|
|
34176
34933
|
...metadata.inputLayout !== void 0 ? { inputLayout: metadata.inputLayout } : {},
|
|
34177
34934
|
...metadata.inputNormalization !== void 0 ? { inputNormalization: metadata.inputNormalization } : {},
|
|
34178
34935
|
...metadata.preprocessMode !== void 0 ? { preprocessMode: metadata.preprocessMode } : {},
|
|
34179
|
-
...metadata.faceAlignment !== void 0 ? { faceAlignment: metadata.faceAlignment } : {}
|
|
34936
|
+
...metadata.faceAlignment !== void 0 ? { faceAlignment: metadata.faceAlignment } : {},
|
|
34937
|
+
...metadata.classMap !== void 0 ? { classMap: metadata.classMap } : {}
|
|
34180
34938
|
};
|
|
34181
34939
|
}
|
|
34182
34940
|
function buildModelConvertProvider(deps) {
|
|
@@ -34713,6 +35471,45 @@ var ModelNodeAvailabilitySchema = object({
|
|
|
34713
35471
|
});
|
|
34714
35472
|
/** `modelId → nodeId → availability`. */
|
|
34715
35473
|
var ModelAvailabilityMapSchema = record(string(), record(string(), ModelNodeAvailabilitySchema));
|
|
35474
|
+
var ScryptedManifestFileSchema = object({
|
|
35475
|
+
relativePath: string(),
|
|
35476
|
+
size: number(),
|
|
35477
|
+
sha256: string().regex(/^[0-9a-f]{64}$/)
|
|
35478
|
+
});
|
|
35479
|
+
var ScryptedManifestEntrySchema = object({
|
|
35480
|
+
id: string(),
|
|
35481
|
+
label: string(),
|
|
35482
|
+
family: _enum([
|
|
35483
|
+
"yolov9t",
|
|
35484
|
+
"yolov9s",
|
|
35485
|
+
"yolov9m",
|
|
35486
|
+
"yolov9c"
|
|
35487
|
+
]),
|
|
35488
|
+
variant: _enum(["relu", "relu_test"]),
|
|
35489
|
+
format: _enum([
|
|
35490
|
+
"coreml",
|
|
35491
|
+
"onnx",
|
|
35492
|
+
"openvino",
|
|
35493
|
+
"tflite"
|
|
35494
|
+
]),
|
|
35495
|
+
precision: string(),
|
|
35496
|
+
inputSize: object({
|
|
35497
|
+
width: number(),
|
|
35498
|
+
height: number()
|
|
35499
|
+
}),
|
|
35500
|
+
files: array(ScryptedManifestFileSchema).readonly(),
|
|
35501
|
+
model: ModelCatalogEntrySchema
|
|
35502
|
+
});
|
|
35503
|
+
var ScryptedCatalogDriftSchema = object({
|
|
35504
|
+
manifestId: string(),
|
|
35505
|
+
status: _enum([
|
|
35506
|
+
"ok",
|
|
35507
|
+
"missing",
|
|
35508
|
+
"size-changed",
|
|
35509
|
+
"unavailable"
|
|
35510
|
+
]),
|
|
35511
|
+
details: string().optional()
|
|
35512
|
+
});
|
|
34716
35513
|
var modelStudioActions = defineCustomActions({
|
|
34717
35514
|
registerCustomModel: customAction(CustomModelDescriptorSchema, object({ ok: literal(true) }), { kind: "mutation" }),
|
|
34718
35515
|
removeCustomModel: customAction(object({ modelId: string() }), object({ removed: boolean() }), { kind: "mutation" }),
|
|
@@ -34779,6 +35576,16 @@ var modelStudioActions = defineCustomActions({
|
|
|
34779
35576
|
bytes: number()
|
|
34780
35577
|
}), { kind: "mutation" }),
|
|
34781
35578
|
/**
|
|
35579
|
+
* Local Scrypted YOLO catalog (Task 7B). Listing is a projection of the
|
|
35580
|
+
* revision-pinned manifesto plus a read-only HF drift check. Import accepts
|
|
35581
|
+
* ONLY `manifestId` — host/repo/revision/path/labels/checksum stay server-owned.
|
|
35582
|
+
*/
|
|
35583
|
+
listScryptedCatalog: customAction(_void(), object({
|
|
35584
|
+
entries: array(ScryptedManifestEntrySchema).readonly(),
|
|
35585
|
+
drift: array(ScryptedCatalogDriftSchema).readonly()
|
|
35586
|
+
})),
|
|
35587
|
+
importScryptedCatalogModel: customAction(object({ manifestId: string() }), CustomModelDescriptorSchema, { kind: "mutation" }),
|
|
35588
|
+
/**
|
|
34782
35589
|
* Frigate+ personal models. The API key lives in the addon's SETTINGS
|
|
34783
35590
|
* (password field) and never leaves the hub: list / test / import all run
|
|
34784
35591
|
* server-side against api.frigate.video. Status deliberately reveals only
|
|
@@ -35043,6 +35850,8 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
|
|
|
35043
35850
|
notes: c.notes
|
|
35044
35851
|
})),
|
|
35045
35852
|
importFrigateCatalogModel: async (input) => this.importFrigateCatalogModel(input),
|
|
35853
|
+
listScryptedCatalog: async () => this.listScryptedCatalog(),
|
|
35854
|
+
importScryptedCatalogModel: async (input) => this.importScryptedCatalogFromManifest(input),
|
|
35046
35855
|
tfliteSupport: async () => {
|
|
35047
35856
|
const api = this.ctx.api;
|
|
35048
35857
|
if (!api) return { nodes: [] };
|
|
@@ -35235,6 +36044,78 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
|
|
|
35235
36044
|
return hash.digest("hex");
|
|
35236
36045
|
}
|
|
35237
36046
|
/**
|
|
36047
|
+
* Projection of the local Scrypted YOLO manifesto plus a read-only HF drift
|
|
36048
|
+
* check. Network errors become `unavailable` rows — the manifesto is never
|
|
36049
|
+
* mutated and nothing is auto-imported.
|
|
36050
|
+
*/
|
|
36051
|
+
async listScryptedCatalog() {
|
|
36052
|
+
try {
|
|
36053
|
+
return {
|
|
36054
|
+
entries: SCRYPTED_YOLO_MANIFEST,
|
|
36055
|
+
drift: compareScryptedManifestDrift(SCRYPTED_YOLO_MANIFEST, await fetchScryptedRemoteTree({ fetchImpl: fetch })).map(normalizeScryptedDrift)
|
|
36056
|
+
};
|
|
36057
|
+
} catch (err) {
|
|
36058
|
+
const details = err instanceof Error ? err.message : "Hugging Face tree unavailable";
|
|
36059
|
+
return {
|
|
36060
|
+
entries: SCRYPTED_YOLO_MANIFEST,
|
|
36061
|
+
drift: SCRYPTED_YOLO_MANIFEST.map((entry) => ({
|
|
36062
|
+
manifestId: entry.id,
|
|
36063
|
+
status: "unavailable",
|
|
36064
|
+
details
|
|
36065
|
+
}))
|
|
36066
|
+
};
|
|
36067
|
+
}
|
|
36068
|
+
}
|
|
36069
|
+
/**
|
|
36070
|
+
* Import one manifesto entry by id. Host/repo/revision/path stay server-owned.
|
|
36071
|
+
*/
|
|
36072
|
+
async importScryptedCatalogFromManifest(input) {
|
|
36073
|
+
const modelsDir = await this.resolveModelsDir();
|
|
36074
|
+
const descriptor = await importScryptedCatalogModel(input, {
|
|
36075
|
+
stagingDir: node_path.default.join(this.ctx.dataDir ?? node_os.default.tmpdir(), "scrypted-import"),
|
|
36076
|
+
modelsDir,
|
|
36077
|
+
downloadFile: (url, destPath) => downloadFile(url, destPath, {
|
|
36078
|
+
redirectPolicy: assertScryptedDownloadUrl,
|
|
36079
|
+
maxBytes: SCRYPTED_IMPORT_MAX_FILE_BYTES,
|
|
36080
|
+
timeoutMs: 6e4
|
|
36081
|
+
}),
|
|
36082
|
+
driftFor: async (manifestId) => {
|
|
36083
|
+
try {
|
|
36084
|
+
const tree = await fetchScryptedRemoteTree({ fetchImpl: fetch });
|
|
36085
|
+
const row = compareScryptedManifestDrift(SCRYPTED_YOLO_MANIFEST.filter((entry) => entry.id === manifestId), tree)[0];
|
|
36086
|
+
return normalizeScryptedDrift(row ?? {
|
|
36087
|
+
manifestId,
|
|
36088
|
+
status: "missing",
|
|
36089
|
+
details: "entry missing from manifesto"
|
|
36090
|
+
});
|
|
36091
|
+
} catch (err) {
|
|
36092
|
+
return {
|
|
36093
|
+
manifestId,
|
|
36094
|
+
status: "unavailable",
|
|
36095
|
+
details: err instanceof Error ? err.message : "Hugging Face tree unavailable"
|
|
36096
|
+
};
|
|
36097
|
+
}
|
|
36098
|
+
}
|
|
36099
|
+
});
|
|
36100
|
+
await this.registerCustomModel(descriptor);
|
|
36101
|
+
const format = resolveScryptedImportedFormat(input.manifestId, descriptor);
|
|
36102
|
+
const manifestEntry = getScryptedManifestEntry(input.manifestId);
|
|
36103
|
+
if (!manifestEntry) throw new Error(`Imported Scrypted catalog model is missing manifesto entry ${input.manifestId}`);
|
|
36104
|
+
const bytes = Math.round((descriptor.entry.formats[format]?.sizeMB ?? 0) * 1e6);
|
|
36105
|
+
await this.availability_.update((prev) => upsertAvailability(prev, descriptor.entry.id, "hub", {
|
|
36106
|
+
format,
|
|
36107
|
+
sha256: scryptedBundleFingerprint(manifestEntry),
|
|
36108
|
+
bytes,
|
|
36109
|
+
at: Date.now()
|
|
36110
|
+
}));
|
|
36111
|
+
this.ctx.logger.info("Imported Scrypted catalog model", { meta: {
|
|
36112
|
+
manifestId: input.manifestId,
|
|
36113
|
+
modelId: descriptor.entry.id,
|
|
36114
|
+
bytes
|
|
36115
|
+
} });
|
|
36116
|
+
return descriptor;
|
|
36117
|
+
}
|
|
36118
|
+
/**
|
|
35238
36119
|
* Import a curated PUBLIC Frigate catalog model: download SERVER-SIDE into
|
|
35239
36120
|
* the hub's modelsDir (size-verified against the curated declaration),
|
|
35240
36121
|
* register the descriptor with the PUBLIC url (nodes download on demand),
|