@camstack/system 1.0.2
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/addon/addon-api-factory.d.ts +35 -0
- package/dist/addon-routes/addon-route-registry.d.ts +37 -0
- package/dist/addon-runner.js +599 -0
- package/dist/addon-runner.mjs +597 -0
- package/dist/auth/api-key-manager.d.ts +26 -0
- package/dist/auth/auth-manager.d.ts +109 -0
- package/dist/auth/parse-record.d.ts +18 -0
- package/dist/auth/scope-matcher.d.ts +7 -0
- package/dist/auth/scoped-token-manager.d.ts +40 -0
- package/dist/auth/totp-manager.d.ts +51 -0
- package/dist/auth/user-manager.d.ts +34 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.d.ts +53 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +259 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +251 -0
- package/dist/builtins/addon-pages-aggregator/dedupe-pages.d.ts +6 -0
- package/dist/builtins/addon-pages-aggregator/index.d.ts +1 -0
- package/dist/builtins/addon-pages-aggregator/index.js +8 -0
- package/dist/builtins/addon-pages-aggregator/index.mjs +2 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.d.ts +47 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +228 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +220 -0
- package/dist/builtins/addon-widgets-aggregator/index.d.ts +1 -0
- package/dist/builtins/addon-widgets-aggregator/index.js +8 -0
- package/dist/builtins/addon-widgets-aggregator/index.mjs +2 -0
- package/dist/builtins/alerts/alerts.addon.d.ts +81 -0
- package/dist/builtins/alerts/alerts.addon.js +601 -0
- package/dist/builtins/alerts/alerts.addon.mjs +595 -0
- package/dist/builtins/alerts/index.d.ts +1 -0
- package/dist/builtins/alerts/index.js +4 -0
- package/dist/builtins/alerts/index.mjs +2 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +147 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2229 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2220 -0
- package/dist/builtins/backup-orchestrator/cron-helpers.d.ts +23 -0
- package/dist/builtins/backup-orchestrator/destination-policy.d.ts +72 -0
- package/dist/builtins/backup-orchestrator/download-helpers.d.ts +12 -0
- package/dist/builtins/backup-orchestrator/index.d.ts +2 -0
- package/dist/builtins/backup-orchestrator/index.js +8 -0
- package/dist/builtins/backup-orchestrator/index.mjs +2 -0
- package/dist/builtins/backup-orchestrator/manifest-store.d.ts +77 -0
- package/dist/builtins/console-logging/console-destination.d.ts +13 -0
- package/dist/builtins/console-logging/console-logging.addon.d.ts +25 -0
- package/dist/builtins/console-logging/index.d.ts +3 -0
- package/dist/builtins/console-logging/index.js +104 -0
- package/dist/builtins/console-logging/index.mjs +95 -0
- package/dist/builtins/device-manager/device-config-contribution.d.ts +32 -0
- package/dist/builtins/device-manager/device-event-propagator.d.ts +26 -0
- package/dist/builtins/device-manager/device-link-overlay.d.ts +23 -0
- package/dist/builtins/device-manager/device-link-resolver.d.ts +15 -0
- package/dist/builtins/device-manager/device-manager.addon.d.ts +452 -0
- package/dist/builtins/device-manager/device-manager.addon.js +3299 -0
- package/dist/builtins/device-manager/device-manager.addon.mjs +3292 -0
- package/dist/builtins/device-manager/index.d.ts +2 -0
- package/dist/builtins/device-manager/index.js +8 -0
- package/dist/builtins/device-manager/index.mjs +2 -0
- package/dist/builtins/hub-forwarder/hub-forwarder-destination.d.ts +44 -0
- package/dist/builtins/hub-forwarder/hub-forwarder.addon.d.ts +15 -0
- package/dist/builtins/hub-forwarder/index.d.ts +3 -0
- package/dist/builtins/hub-forwarder/index.js +154 -0
- package/dist/builtins/hub-forwarder/index.mjs +145 -0
- package/dist/builtins/local-auth/auth-schema.d.ts +26 -0
- package/dist/builtins/local-auth/index.d.ts +1 -0
- package/dist/builtins/local-auth/index.js +4 -0
- package/dist/builtins/local-auth/index.mjs +2 -0
- package/dist/builtins/local-auth/local-auth.addon.d.ts +18 -0
- package/dist/builtins/local-auth/local-auth.addon.js +8094 -0
- package/dist/builtins/local-auth/local-auth.addon.mjs +8063 -0
- package/dist/builtins/local-auth/oauth-grants.d.ts +45 -0
- package/dist/builtins/local-auth/oauth-session-manager.d.ts +50 -0
- package/dist/builtins/local-network/index.d.ts +2 -0
- package/dist/builtins/local-network/index.js +10 -0
- package/dist/builtins/local-network/index.mjs +2 -0
- package/dist/builtins/local-network/local-network.addon.d.ts +150 -0
- package/dist/builtins/local-network/local-network.addon.js +489 -0
- package/dist/builtins/local-network/local-network.addon.mjs +477 -0
- package/dist/builtins/native-metrics/index.d.ts +2 -0
- package/dist/builtins/native-metrics/native-metrics-provider.d.ts +48 -0
- package/dist/builtins/native-metrics/native-metrics.addon.d.ts +73 -0
- package/dist/builtins/native-metrics/native-metrics.addon.js +922 -0
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +914 -0
- package/dist/builtins/platform-probe/hardware-decode-accel-probe.d.ts +37 -0
- package/dist/builtins/platform-probe/hardware-encoder-probe.d.ts +13 -0
- package/dist/builtins/platform-probe/index.d.ts +22 -0
- package/dist/builtins/platform-probe/index.js +834 -0
- package/dist/builtins/platform-probe/index.mjs +822 -0
- package/dist/builtins/platform-probe/inference-config-resolver.d.ts +29 -0
- package/dist/builtins/platform-probe/intel-accelerators.d.ts +11 -0
- package/dist/builtins/platform-probe/platform-scorer.d.ts +30 -0
- package/dist/builtins/platform-probe/runtime-packages.d.ts +6 -0
- package/dist/builtins/remote-access-orchestrator/enabled-providers-reconcile.d.ts +96 -0
- package/dist/builtins/remote-access-orchestrator/index.d.ts +1 -0
- package/dist/builtins/remote-access-orchestrator/index.js +8 -0
- package/dist/builtins/remote-access-orchestrator/index.mjs +2 -0
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.d.ts +40 -0
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +214 -0
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +208 -0
- package/dist/builtins/shared/settle-sources.d.ts +22 -0
- package/dist/builtins/snapshot/index.d.ts +2 -0
- package/dist/builtins/snapshot/index.js +494 -0
- package/dist/builtins/snapshot/index.mjs +488 -0
- package/dist/builtins/snapshot/snapshot.addon.d.ts +120 -0
- package/dist/builtins/sqlite-storage/config-store.d.ts +8 -0
- package/dist/builtins/sqlite-storage/device-store.d.ts +23 -0
- package/dist/builtins/sqlite-storage/filesystem-browse-provider.d.ts +25 -0
- package/dist/builtins/sqlite-storage/filesystem-storage-provider.d.ts +83 -0
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.ts +32 -0
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +396 -0
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +388 -0
- package/dist/builtins/sqlite-storage/index.d.ts +8 -0
- package/dist/builtins/sqlite-storage/index.js +62 -0
- package/dist/builtins/sqlite-storage/index.mjs +49 -0
- package/dist/builtins/sqlite-storage/integration-registry.d.ts +27 -0
- package/dist/builtins/sqlite-storage/path-guard.d.ts +4 -0
- package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +102 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +14 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +644 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +636 -0
- package/dist/builtins/storage-orchestrator/index.d.ts +6 -0
- package/dist/builtins/storage-orchestrator/index.js +10 -0
- package/dist/builtins/storage-orchestrator/index.mjs +2 -0
- package/dist/builtins/storage-orchestrator/location-store.d.ts +49 -0
- package/dist/builtins/storage-orchestrator/provider-discovery.d.ts +10 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +103 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1138 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1128 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +236 -0
- package/dist/builtins/storage-orchestrator/storage-pressure-manager.d.ts +38 -0
- package/dist/builtins/system-backup/system-backup.service.d.ts +137 -0
- package/dist/builtins/system-config/index.d.ts +1 -0
- package/dist/builtins/system-config/index.js +8 -0
- package/dist/builtins/system-config/index.mjs +2 -0
- package/dist/builtins/system-config/system-config.addon.d.ts +10 -0
- package/dist/builtins/system-config/system-config.addon.js +232 -0
- package/dist/builtins/system-config/system-config.addon.mjs +226 -0
- package/dist/builtins/winston-logging/index.d.ts +3 -0
- package/dist/builtins/winston-logging/index.js +156 -0
- package/dist/builtins/winston-logging/index.mjs +144 -0
- package/dist/builtins/winston-logging/winston-destination.d.ts +21 -0
- package/dist/builtins/winston-logging/winston-logging.addon.d.ts +19 -0
- package/dist/chunk-CNf5ZN-e.mjs +37 -0
- package/dist/chunk-Cek0wNdY.js +64 -0
- package/dist/download/model-download-service.d.ts +41 -0
- package/dist/download/model-downloader.d.ts +31 -0
- package/dist/events/event-bus.d.ts +10 -0
- package/dist/events/system-event-bus.d.ts +14 -0
- package/dist/feature/feature-manager.d.ts +11 -0
- package/dist/formatter-B7qW8bPJ.mjs +162 -0
- package/dist/formatter-DqAKDlvN.js +167 -0
- package/dist/http/authenticated-file-server.d.ts +53 -0
- package/dist/http/data-plane-registry.d.ts +23 -0
- package/dist/http/file-data-plane.d.ts +10 -0
- package/dist/http/reverse-proxy.d.ts +15 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.js +93485 -0
- package/dist/index.mjs +93179 -0
- package/dist/intel-accelerators-Gg0P5mnl.js +20 -0
- package/dist/intel-accelerators-hGgpZ0pX.mjs +19 -0
- package/dist/kernel/addon-class-resolver.d.ts +4 -0
- package/dist/kernel/addon-engine-manager.d.ts +22 -0
- package/dist/kernel/addon-health-monitor.d.ts +154 -0
- package/dist/kernel/addon-installer.d.ts +208 -0
- package/dist/kernel/addon-loader.d.ts +106 -0
- package/dist/kernel/addon-manifest.d.ts +77 -0
- package/dist/kernel/capability-handle.d.ts +46 -0
- package/dist/kernel/capability-registry.d.ts +412 -0
- package/dist/kernel/config-manager.d.ts +212 -0
- package/dist/kernel/config-schema.d.ts +93 -0
- package/dist/kernel/custom-action-registry.d.ts +23 -0
- package/dist/kernel/deps/addon-deps-manager.d.ts +19 -0
- package/dist/kernel/deps/manifest-native-deps.d.ts +25 -0
- package/dist/kernel/deps/manifest-python-deps.d.ts +20 -0
- package/dist/kernel/device-registry.d.ts +29 -0
- package/dist/kernel/fs-utils.d.ts +41 -0
- package/dist/kernel/hwaccel/hwaccel-resolver.d.ts +19 -0
- package/dist/kernel/hwaccel/hwaccel-service.d.ts +4 -0
- package/dist/kernel/index.d.ts +74 -0
- package/dist/kernel/infra-capabilities.d.ts +13 -0
- package/dist/kernel/moleculer/addon-context-factory.d.ts +91 -0
- package/dist/kernel/moleculer/addon-data-plane-facility.d.ts +19 -0
- package/dist/kernel/moleculer/addon-runner.d.ts +1 -0
- package/dist/kernel/moleculer/addon-service-factory.d.ts +50 -0
- package/dist/kernel/moleculer/broker-factory.d.ts +50 -0
- package/dist/kernel/moleculer/cap-usage-registry.d.ts +46 -0
- package/dist/kernel/moleculer/capabilities-access.d.ts +21 -0
- package/dist/kernel/moleculer/child-addon-call-dispatch.d.ts +46 -0
- package/dist/kernel/moleculer/child-cap-dispatch.d.ts +20 -0
- package/dist/kernel/moleculer/cluster-secret.d.ts +15 -0
- package/dist/kernel/moleculer/core-cap-service.d.ts +50 -0
- package/dist/kernel/moleculer/crash-supervisor.d.ts +50 -0
- package/dist/kernel/moleculer/device-cap-proxy.d.ts +79 -0
- package/dist/kernel/moleculer/event-bus-core.d.ts +53 -0
- package/dist/kernel/moleculer/event-bus.d.ts +53 -0
- package/dist/kernel/moleculer/hub-log-forwarder.d.ts +36 -0
- package/dist/kernel/moleculer/hub-service.d.ts +35 -0
- package/dist/kernel/moleculer/node-registry.d.ts +126 -0
- package/dist/kernel/moleculer/process-context.d.ts +4 -0
- package/dist/kernel/moleculer/process-service.d.ts +72 -0
- package/dist/kernel/moleculer/provider-registry.d.ts +28 -0
- package/dist/kernel/moleculer/readiness-context.d.ts +62 -0
- package/dist/kernel/moleculer/readiness-service.d.ts +7 -0
- package/dist/kernel/moleculer/register-node-client.d.ts +35 -0
- package/dist/kernel/moleculer/remote-logger.d.ts +43 -0
- package/dist/kernel/moleculer/resilient-cap-call.d.ts +28 -0
- package/dist/kernel/moleculer/stream-probe-service.d.ts +9 -0
- package/dist/kernel/moleculer/trpc-links.d.ts +189 -0
- package/dist/kernel/moleculer/typed-array-serde.d.ts +25 -0
- package/dist/kernel/moleculer/worker-device-restore.d.ts +10 -0
- package/dist/kernel/provider-kind-drift.d.ts +12 -0
- package/dist/kernel/restart-coordinator.d.ts +90 -0
- package/dist/kernel/storage-location-registry.d.ts +40 -0
- package/dist/kernel/transport/cap-action-name.d.ts +100 -0
- package/dist/kernel/transport/cap-route-resolver.d.ts +148 -0
- package/dist/kernel/transport/cap-route.d.ts +148 -0
- package/dist/kernel/transport/child-cap-protocol.d.ts +136 -0
- package/dist/kernel/transport/create-local-transport.d.ts +7 -0
- package/dist/kernel/transport/frame-codec.d.ts +7 -0
- package/dist/kernel/transport/index.d.ts +27 -0
- package/dist/kernel/transport/local-child-client.d.ts +136 -0
- package/dist/kernel/transport/local-child-registry.d.ts +179 -0
- package/dist/kernel/transport/local-endpoint-path.d.ts +6 -0
- package/dist/kernel/transport/local-transport.d.ts +46 -0
- package/dist/kernel/transport/parent-unowned-call.d.ts +75 -0
- package/dist/kernel/transport/socket-channel.d.ts +27 -0
- package/dist/kernel/transport/uds-event-bridge.d.ts +36 -0
- package/dist/kernel/transport/uds-event-bus.d.ts +22 -0
- package/dist/kernel/transport/uds-local-transport.d.ts +18 -0
- package/dist/kernel/transport/uds-log-ingest.d.ts +28 -0
- package/dist/kernel/transport/uds-logger.d.ts +44 -0
- package/dist/kernel/utils/ring-buffer.d.ts +15 -0
- package/dist/kernel/workspace-detect.d.ts +9 -0
- package/dist/lifecycle/lifecycle-state-machine.d.ts +28 -0
- package/dist/logging/formatter.d.ts +30 -0
- package/dist/logging/log-manager.d.ts +54 -0
- package/dist/logging/log-ring-buffer.d.ts +47 -0
- package/dist/logging/partitioned-log-buffer.d.ts +35 -0
- package/dist/logging/scoped-logger.d.ts +17 -0
- package/dist/main-DNnMW7Z2.js +9983 -0
- package/dist/main-rtjOwPBR.mjs +9976 -0
- package/dist/manifest-python-deps-D1DbAQEv.js +6724 -0
- package/dist/manifest-python-deps-DZsKTbs1.mjs +6315 -0
- package/dist/network/network-quality.d.ts +11 -0
- package/dist/notification/notification-service.d.ts +37 -0
- package/dist/notification/toast-service.d.ts +22 -0
- package/dist/pipeline/engine-manager-resolver.d.ts +15 -0
- package/dist/pipeline/pipeline-runner.d.ts +8 -0
- package/dist/pipeline/pipeline-validator.d.ts +13 -0
- package/dist/process/resource-monitor.d.ts +11 -0
- package/dist/python/python-env-manager.d.ts +12 -0
- package/dist/repl/interfaces.d.ts +31 -0
- package/dist/repl/repl-engine.d.ts +8 -0
- package/dist/resource-monitor-ClDGFyf6.mjs +57 -0
- package/dist/resource-monitor-IIEanuJt.js +74 -0
- package/dist/settle-sources-Bhsy57y-.js +38 -0
- package/dist/settle-sources-CDtNC8ub.mjs +33 -0
- package/dist/storage/fs-storage-backend.d.ts +40 -0
- package/dist/storage/storage-location-manager.d.ts +23 -0
- package/dist/storage/storage-manager.d.ts +83 -0
- package/dist/tar-BgAEMRBR.js +5434 -0
- package/dist/tar-ByMOPNM0.mjs +5429 -0
- package/dist/tls/cert-manager.d.ts +26 -0
- package/dist/tls/index.d.ts +1 -0
- package/package.json +343 -0
|
@@ -0,0 +1,822 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { BaseAddon, EventCategory, errMsg, platformProbeCapability } from "@camstack/types";
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import * as os from "node:os";
|
|
6
|
+
//#region src/builtins/platform-probe/platform-scorer.ts
|
|
7
|
+
/**
|
|
8
|
+
* Promisified `execFile`. Used across the scorer so every subprocess
|
|
9
|
+
* spawn yields the event loop — critical at boot because a single sync
|
|
10
|
+
* spawn of a missing Python module hits its 30s timeout and freezes the
|
|
11
|
+
* entire Node process (WS handshakes, tRPC subscriptions, metrics — all
|
|
12
|
+
* stalled). Keeping it async turns the probe into a true background task.
|
|
13
|
+
*/
|
|
14
|
+
var execFileAsync$2 = promisify(execFile);
|
|
15
|
+
/** Minimal no-op logger for default parameter */
|
|
16
|
+
var noopLogger = {
|
|
17
|
+
debug() {},
|
|
18
|
+
info() {},
|
|
19
|
+
warn() {},
|
|
20
|
+
error() {},
|
|
21
|
+
child() {
|
|
22
|
+
return noopLogger;
|
|
23
|
+
},
|
|
24
|
+
withTags() {
|
|
25
|
+
return noopLogger;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Get reliable "available" RAM in MB, cross-platform.
|
|
30
|
+
* os.freemem() is unreliable on macOS (reports ~200MB on a 36GB system).
|
|
31
|
+
* Uses systeminformation when available, falls back to native commands.
|
|
32
|
+
*/
|
|
33
|
+
async function getAvailableRAM_MB() {
|
|
34
|
+
try {
|
|
35
|
+
const mem = await (await import("systeminformation")).mem();
|
|
36
|
+
return Math.round(mem.available / 1024 / 1024);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.debug(`systeminformation not available, using platform-specific RAM probe: ${errMsg(err)}`);
|
|
39
|
+
const platform = os.platform();
|
|
40
|
+
try {
|
|
41
|
+
if (platform === "darwin") {
|
|
42
|
+
const { stdout: output } = await execFileAsync$2("vm_stat", [], {
|
|
43
|
+
encoding: "utf8",
|
|
44
|
+
timeout: 3e3
|
|
45
|
+
});
|
|
46
|
+
const pageSize = parseInt(output.match(/page size of (\d+)/)?.[1] ?? "16384");
|
|
47
|
+
const free = parseInt(output.match(/Pages free:\s+(\d+)/)?.[1] ?? "0");
|
|
48
|
+
const inactive = parseInt(output.match(/Pages inactive:\s+(\d+)/)?.[1] ?? "0");
|
|
49
|
+
const purgeable = parseInt(output.match(/Pages purgeable:\s+(\d+)/)?.[1] ?? "0");
|
|
50
|
+
return Math.round((free + inactive + purgeable) * pageSize / 1024 / 1024);
|
|
51
|
+
} else if (platform === "linux") {
|
|
52
|
+
const { readFileSync } = await import("node:fs");
|
|
53
|
+
const match = readFileSync("/proc/meminfo", "utf8").match(/MemAvailable:\s+(\d+)\s+kB/);
|
|
54
|
+
if (match) return Math.round(parseInt(match[1]) / 1024);
|
|
55
|
+
}
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.debug(`RAM probe failed, using total RAM fallback: ${errMsg(err)}`);
|
|
58
|
+
}
|
|
59
|
+
return Math.round(os.totalmem() / 1024 / 1024);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
var PlatformScorer = class {
|
|
63
|
+
cached = null;
|
|
64
|
+
logger;
|
|
65
|
+
/**
|
|
66
|
+
* Path to the embedded portable Python (from `ctx.deps.ensurePython()`).
|
|
67
|
+
* The Docker hub ships NO system `python3` — inference runs on the
|
|
68
|
+
* downloaded portable build — so probing system `python3`/`python` would
|
|
69
|
+
* wrongly report "Python not found" and never surface coreml/openvino
|
|
70
|
+
* backends. When set, the Python module probes run against THIS binary;
|
|
71
|
+
* `null` falls back to a system `python3`/`python` lookup (dev / agent
|
|
72
|
+
* environments where Python is on PATH).
|
|
73
|
+
*/
|
|
74
|
+
embeddedPythonPath;
|
|
75
|
+
constructor(logger = noopLogger, embeddedPythonPath = null) {
|
|
76
|
+
this.logger = logger;
|
|
77
|
+
this.embeddedPythonPath = embeddedPythonPath;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Probe hardware + runtimes and score all backend combos.
|
|
81
|
+
*
|
|
82
|
+
* An optional `onPhase` callback is invoked at each step so consumers
|
|
83
|
+
* (e.g. the platform-probe addon) can emit live progress events on
|
|
84
|
+
* the event bus. The callback takes a phase id + a typed payload; all
|
|
85
|
+
* phases fire in strict order: `started` → `hardware` → `node-backends`
|
|
86
|
+
* → `python-backends` → `scored` → `done`. On failure, `error` fires
|
|
87
|
+
* once with the exception. Cached after first call.
|
|
88
|
+
*/
|
|
89
|
+
async probe(onPhase) {
|
|
90
|
+
if (this.cached) return this.cached;
|
|
91
|
+
const start = Date.now();
|
|
92
|
+
onPhase?.("started", {});
|
|
93
|
+
this.logger.info("Probing hardware...");
|
|
94
|
+
const hardware = await this.probeHardware();
|
|
95
|
+
this.logger.info("Hardware detected", { meta: {
|
|
96
|
+
platform: hardware.platform,
|
|
97
|
+
arch: hardware.arch,
|
|
98
|
+
cpuModel: hardware.cpuModel,
|
|
99
|
+
cpuCores: hardware.cpuCores,
|
|
100
|
+
ramGB: Math.round(hardware.totalRAM_MB / 1024)
|
|
101
|
+
} });
|
|
102
|
+
if (hardware.gpu) this.logger.info("GPU detected", { meta: { name: hardware.gpu.name } });
|
|
103
|
+
if (hardware.npu) this.logger.info("NPU detected", { meta: { type: hardware.npu.type } });
|
|
104
|
+
onPhase?.("hardware", { hardware });
|
|
105
|
+
this.logger.info("Probing Python backends...");
|
|
106
|
+
const pythonInfo = await this.probePythonBackends();
|
|
107
|
+
if (pythonInfo.pythonPath) this.logger.info("Python backends detected", { meta: {
|
|
108
|
+
pythonPath: pythonInfo.pythonPath,
|
|
109
|
+
backends: pythonInfo.backends.filter((b) => b.available).map((b) => b.id)
|
|
110
|
+
} });
|
|
111
|
+
else this.logger.info("Python: not found");
|
|
112
|
+
onPhase?.("python-backends", {
|
|
113
|
+
pythonPath: pythonInfo.pythonPath,
|
|
114
|
+
backends: pythonInfo.backends
|
|
115
|
+
});
|
|
116
|
+
const scores = this.scoreBackends(hardware, pythonInfo.backends);
|
|
117
|
+
const bestScore = scores.find((s) => s.available) ?? scores[scores.length - 1];
|
|
118
|
+
const elapsed = Date.now() - start;
|
|
119
|
+
this.logger.info("Scoring complete", { meta: {
|
|
120
|
+
elapsedMs: elapsed,
|
|
121
|
+
combos: scores.length
|
|
122
|
+
} });
|
|
123
|
+
this.logger.info("Best backend selected", { meta: {
|
|
124
|
+
runtime: bestScore.runtime,
|
|
125
|
+
backend: bestScore.backend,
|
|
126
|
+
format: bestScore.format,
|
|
127
|
+
reason: bestScore.reason,
|
|
128
|
+
score: bestScore.score
|
|
129
|
+
} });
|
|
130
|
+
for (const s of scores) this.logger.debug("Score entry", { meta: {
|
|
131
|
+
available: s.available,
|
|
132
|
+
runtime: s.runtime,
|
|
133
|
+
backend: s.backend,
|
|
134
|
+
format: s.format,
|
|
135
|
+
score: s.score,
|
|
136
|
+
reason: s.reason
|
|
137
|
+
} });
|
|
138
|
+
this.cached = {
|
|
139
|
+
hardware,
|
|
140
|
+
scores,
|
|
141
|
+
bestScore,
|
|
142
|
+
pythonPath: pythonInfo.pythonPath
|
|
143
|
+
};
|
|
144
|
+
onPhase?.("scored", {
|
|
145
|
+
scores,
|
|
146
|
+
bestScore,
|
|
147
|
+
elapsedMs: elapsed
|
|
148
|
+
});
|
|
149
|
+
onPhase?.("done", { capabilities: this.cached });
|
|
150
|
+
return this.cached;
|
|
151
|
+
}
|
|
152
|
+
async probeHardware() {
|
|
153
|
+
const platform = os.platform();
|
|
154
|
+
const arch = os.arch();
|
|
155
|
+
const cpus = os.cpus();
|
|
156
|
+
const cpuModel = cpus[0]?.model ?? "unknown";
|
|
157
|
+
const cpuCores = cpus.length;
|
|
158
|
+
const totalRAM_MB = Math.round(os.totalmem() / 1024 / 1024);
|
|
159
|
+
const availableRAM_MB = await getAvailableRAM_MB();
|
|
160
|
+
this.logger.debug("RAM probed", { meta: {
|
|
161
|
+
totalRAM_MB,
|
|
162
|
+
availableRAM_MB
|
|
163
|
+
} });
|
|
164
|
+
let gpu = null;
|
|
165
|
+
let npu = null;
|
|
166
|
+
if (platform === "darwin" && arch === "arm64") {
|
|
167
|
+
gpu = {
|
|
168
|
+
type: "apple",
|
|
169
|
+
name: "Apple Silicon GPU"
|
|
170
|
+
};
|
|
171
|
+
npu = { type: "apple-ane" };
|
|
172
|
+
}
|
|
173
|
+
if (platform === "linux") {
|
|
174
|
+
try {
|
|
175
|
+
const { stdout } = await execFileAsync$2("nvidia-smi", ["--query-gpu=name,memory.total", "--format=csv,noheader"], {
|
|
176
|
+
encoding: "utf8",
|
|
177
|
+
timeout: 5e3
|
|
178
|
+
});
|
|
179
|
+
const output = stdout.trim();
|
|
180
|
+
if (output) {
|
|
181
|
+
const [name, mem] = output.split(",").map((s) => s.trim());
|
|
182
|
+
gpu = {
|
|
183
|
+
type: "nvidia",
|
|
184
|
+
name: name ?? "NVIDIA GPU",
|
|
185
|
+
memoryMB: parseInt(mem ?? "0")
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
} catch (err) {
|
|
189
|
+
this.logger.debug("NVIDIA GPU detection failed", { meta: { error: errMsg(err) } });
|
|
190
|
+
}
|
|
191
|
+
if (!gpu || !npu) {
|
|
192
|
+
const { classifyIntelAccelerators } = await import("../../intel-accelerators-hGgpZ0pX.mjs");
|
|
193
|
+
const intel = classifyIntelAccelerators({
|
|
194
|
+
listRenderNodes: () => {
|
|
195
|
+
try {
|
|
196
|
+
return fs.readdirSync("/dev/dri").filter((n) => n.startsWith("renderD")).map((n) => `/dev/dri/${n}`);
|
|
197
|
+
} catch {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
readVendor: (node) => {
|
|
202
|
+
try {
|
|
203
|
+
return fs.readFileSync(`/sys/class/drm/${node.split("/").pop()}/device/vendor`, "utf8");
|
|
204
|
+
} catch {
|
|
205
|
+
return "";
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
exists: (p) => fs.existsSync(p)
|
|
209
|
+
});
|
|
210
|
+
gpu = gpu ?? intel.gpu;
|
|
211
|
+
npu = npu ?? intel.npu;
|
|
212
|
+
if (intel.npu) this.logger.info("Intel NPU detected", { meta: { device: "/dev/accel/accel0" } });
|
|
213
|
+
if (intel.gpu) this.logger.info("Intel iGPU detected", { meta: { name: intel.gpu.name } });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
platform,
|
|
218
|
+
arch,
|
|
219
|
+
cpuModel,
|
|
220
|
+
cpuCores,
|
|
221
|
+
totalRAM_MB,
|
|
222
|
+
availableRAM_MB,
|
|
223
|
+
gpu,
|
|
224
|
+
npu
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
async probePythonBackends() {
|
|
228
|
+
const candidates = [
|
|
229
|
+
...this.embeddedPythonPath ? [this.embeddedPythonPath] : [],
|
|
230
|
+
"python3",
|
|
231
|
+
"python"
|
|
232
|
+
];
|
|
233
|
+
let pythonPath = null;
|
|
234
|
+
for (const cmd of candidates) try {
|
|
235
|
+
await execFileAsync$2(cmd, ["--version"], { timeout: 5e3 });
|
|
236
|
+
pythonPath = cmd;
|
|
237
|
+
break;
|
|
238
|
+
} catch (err) {
|
|
239
|
+
console.debug(`Python command "${cmd}" not found: ${errMsg(err)}`);
|
|
240
|
+
}
|
|
241
|
+
if (!pythonPath) return {
|
|
242
|
+
pythonPath: null,
|
|
243
|
+
backends: []
|
|
244
|
+
};
|
|
245
|
+
const results = await Promise.all([
|
|
246
|
+
[
|
|
247
|
+
"coremltools",
|
|
248
|
+
"coreml",
|
|
249
|
+
"coreml"
|
|
250
|
+
],
|
|
251
|
+
[
|
|
252
|
+
"openvino.runtime",
|
|
253
|
+
"openvino",
|
|
254
|
+
"openvino"
|
|
255
|
+
],
|
|
256
|
+
[
|
|
257
|
+
"torch",
|
|
258
|
+
"pytorch",
|
|
259
|
+
"onnx"
|
|
260
|
+
],
|
|
261
|
+
[
|
|
262
|
+
"onnxruntime",
|
|
263
|
+
"onnx-py",
|
|
264
|
+
"onnx"
|
|
265
|
+
]
|
|
266
|
+
].map(async ([mod, id, format]) => {
|
|
267
|
+
const probeStart = Date.now();
|
|
268
|
+
let available = false;
|
|
269
|
+
try {
|
|
270
|
+
await execFileAsync$2(pythonPath, ["-c", `import ${mod}`], { timeout: 3e4 });
|
|
271
|
+
available = true;
|
|
272
|
+
this.logger.info("Python backend confirmed", { meta: {
|
|
273
|
+
backend: id,
|
|
274
|
+
module: mod
|
|
275
|
+
} });
|
|
276
|
+
} catch (err) {
|
|
277
|
+
console.debug(`Python module "${mod}" not installed: ${errMsg(err)}`);
|
|
278
|
+
if (id === "openvino") this.logger.info("Python backend not yet confirmed: openvino (may be pending proactive install on Intel hardware — falling back to onnx-cpu)", { meta: { module: mod } });
|
|
279
|
+
}
|
|
280
|
+
const probeMs = Date.now() - probeStart;
|
|
281
|
+
this.logger.debug("Python module probed", { meta: {
|
|
282
|
+
module: mod,
|
|
283
|
+
available,
|
|
284
|
+
probeMs
|
|
285
|
+
} });
|
|
286
|
+
return {
|
|
287
|
+
mod,
|
|
288
|
+
id,
|
|
289
|
+
format,
|
|
290
|
+
available
|
|
291
|
+
};
|
|
292
|
+
}));
|
|
293
|
+
const backends = [];
|
|
294
|
+
for (const r of results) if (r.id === "coreml" && os.platform() === "darwin") backends.push({
|
|
295
|
+
id: r.id,
|
|
296
|
+
format: r.format,
|
|
297
|
+
available: r.available
|
|
298
|
+
});
|
|
299
|
+
else if (r.available) backends.push({
|
|
300
|
+
id: r.id,
|
|
301
|
+
format: r.format,
|
|
302
|
+
available: r.available
|
|
303
|
+
});
|
|
304
|
+
return {
|
|
305
|
+
pythonPath,
|
|
306
|
+
backends
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
scoreBackends(hardware, pythonBackends) {
|
|
310
|
+
const scores = [];
|
|
311
|
+
if (hardware.platform === "darwin" && hardware.arch === "arm64") {
|
|
312
|
+
const pyCoreMl = pythonBackends.find((b) => b.id === "coreml");
|
|
313
|
+
if (pyCoreMl) scores.push({
|
|
314
|
+
runtime: "python",
|
|
315
|
+
backend: "coreml",
|
|
316
|
+
format: "coreml",
|
|
317
|
+
score: 95,
|
|
318
|
+
reason: "Apple Neural Engine (Python CoreML)",
|
|
319
|
+
available: pyCoreMl.available
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
if (hardware.gpu?.type === "nvidia") scores.push({
|
|
323
|
+
runtime: "python",
|
|
324
|
+
backend: "cuda",
|
|
325
|
+
format: "onnx",
|
|
326
|
+
score: 85,
|
|
327
|
+
reason: "NVIDIA CUDA (Python ONNX Runtime)",
|
|
328
|
+
available: true
|
|
329
|
+
});
|
|
330
|
+
const openvino = pythonBackends.find((b) => b.id === "openvino");
|
|
331
|
+
if (openvino) {
|
|
332
|
+
const score = hardware.npu?.type === "intel-npu" ? 90 : 80;
|
|
333
|
+
scores.push({
|
|
334
|
+
runtime: "python",
|
|
335
|
+
backend: "openvino",
|
|
336
|
+
format: "openvino",
|
|
337
|
+
score,
|
|
338
|
+
reason: "Intel OpenVINO",
|
|
339
|
+
available: openvino.available
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
scores.push({
|
|
343
|
+
runtime: "python",
|
|
344
|
+
backend: "cpu",
|
|
345
|
+
format: "onnx",
|
|
346
|
+
score: 50,
|
|
347
|
+
reason: "CPU (Python ONNX Runtime)",
|
|
348
|
+
available: true
|
|
349
|
+
});
|
|
350
|
+
return scores.toSorted((a, b) => b.score - a.score);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
//#endregion
|
|
354
|
+
//#region src/builtins/platform-probe/inference-config-resolver.ts
|
|
355
|
+
var InferenceConfigResolver = class {
|
|
356
|
+
scores;
|
|
357
|
+
hardware;
|
|
358
|
+
constructor(scores, hardware) {
|
|
359
|
+
this.scores = scores;
|
|
360
|
+
this.hardware = hardware;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Compute accuracy/backend weights based on available system RAM.
|
|
364
|
+
* availableRAM_MB is now sourced from systeminformation (reliable cross-platform),
|
|
365
|
+
* not os.freemem() which is broken on macOS.
|
|
366
|
+
*
|
|
367
|
+
* - > 16 GB available: prefer larger, more accurate models (accuracy 0.6, backend 0.4)
|
|
368
|
+
* - > 8 GB available: balanced (accuracy 0.5, backend 0.5)
|
|
369
|
+
* - <= 8 GB available: prefer speed (accuracy 0.4, backend 0.6)
|
|
370
|
+
*/
|
|
371
|
+
getWeights() {
|
|
372
|
+
const ramMB = this.hardware.availableRAM_MB;
|
|
373
|
+
if (ramMB > 16384) return {
|
|
374
|
+
accuracyWeight: .6,
|
|
375
|
+
backendWeight: .4
|
|
376
|
+
};
|
|
377
|
+
if (ramMB > 8192) return {
|
|
378
|
+
accuracyWeight: .5,
|
|
379
|
+
backendWeight: .5
|
|
380
|
+
};
|
|
381
|
+
return {
|
|
382
|
+
accuracyWeight: .4,
|
|
383
|
+
backendWeight: .6
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Given an addon's model requirements, pick the best model + runtime + backend.
|
|
388
|
+
*
|
|
389
|
+
* Algorithm:
|
|
390
|
+
* 1. Filter models by available RAM (minRAM_MB < 25% of available RAM)
|
|
391
|
+
* 2. For each remaining model, find the best platform score whose format
|
|
392
|
+
* is available in the model's formats
|
|
393
|
+
* 3. Pick the model with the highest combined score using RAM-adaptive weights:
|
|
394
|
+
* - High RAM (>16 GB): accuracy × 0.6 + backend × 0.4 (prefer accuracy)
|
|
395
|
+
* - Mid RAM (>8 GB): accuracy × 0.5 + backend × 0.5 (balanced)
|
|
396
|
+
* - Low RAM (<=8 GB): accuracy × 0.4 + backend × 0.6 (prefer speed)
|
|
397
|
+
*/
|
|
398
|
+
resolve(requirements) {
|
|
399
|
+
if (requirements.length === 0) return {
|
|
400
|
+
modelId: "",
|
|
401
|
+
runtime: "node",
|
|
402
|
+
backend: "cpu",
|
|
403
|
+
format: "onnx",
|
|
404
|
+
reason: "No models declared"
|
|
405
|
+
};
|
|
406
|
+
const ramBudget = this.hardware.availableRAM_MB * .25;
|
|
407
|
+
const { accuracyWeight, backendWeight } = this.getWeights();
|
|
408
|
+
console.log(`[InferenceConfigResolver] availableRAM: ${this.hardware.availableRAM_MB}MB, budget: ${Math.round(ramBudget)}MB, weights: accuracy=${accuracyWeight}, backend=${backendWeight}`);
|
|
409
|
+
const fits = requirements.filter((m) => m.minRAM_MB < ramBudget);
|
|
410
|
+
const candidates = fits.length > 0 ? fits : [requirements[0]];
|
|
411
|
+
console.log(`[InferenceConfigResolver] ${candidates.length}/${requirements.length} models fit RAM budget`);
|
|
412
|
+
let bestCombo = null;
|
|
413
|
+
for (const model of candidates) for (const score of this.scores) {
|
|
414
|
+
if (!score.available) continue;
|
|
415
|
+
if (!model.formats.includes(score.format)) continue;
|
|
416
|
+
const combined = model.accuracyScore * accuracyWeight + score.score * backendWeight;
|
|
417
|
+
if (!bestCombo || combined > bestCombo.combined) {
|
|
418
|
+
console.log(`[InferenceConfigResolver] New best: ${model.modelId} (accuracy=${model.accuracyScore}) + ${score.backend}/${score.format} (score=${score.score}) → combined=${Math.round(combined)}`);
|
|
419
|
+
bestCombo = {
|
|
420
|
+
model,
|
|
421
|
+
score,
|
|
422
|
+
combined
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (!bestCombo) return {
|
|
427
|
+
modelId: candidates[0].modelId,
|
|
428
|
+
runtime: "node",
|
|
429
|
+
backend: "cpu",
|
|
430
|
+
format: "onnx",
|
|
431
|
+
reason: "No compatible backend — CPU fallback"
|
|
432
|
+
};
|
|
433
|
+
return {
|
|
434
|
+
modelId: bestCombo.model.modelId,
|
|
435
|
+
runtime: bestCombo.score.runtime,
|
|
436
|
+
backend: bestCombo.score.backend,
|
|
437
|
+
format: bestCombo.score.format,
|
|
438
|
+
reason: `${bestCombo.model.name} on ${bestCombo.score.reason} (score: ${Math.round(bestCombo.combined)})`
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
//#endregion
|
|
443
|
+
//#region src/builtins/platform-probe/hardware-encoder-probe.ts
|
|
444
|
+
var execFileAsync$1 = promisify(execFile);
|
|
445
|
+
function buildCandidates(hardware) {
|
|
446
|
+
const out = [];
|
|
447
|
+
if (hardware.platform === "darwin") {
|
|
448
|
+
out.push({
|
|
449
|
+
encoder: "h264_videotoolbox",
|
|
450
|
+
codec: "H264",
|
|
451
|
+
family: "videotoolbox"
|
|
452
|
+
});
|
|
453
|
+
out.push({
|
|
454
|
+
encoder: "hevc_videotoolbox",
|
|
455
|
+
codec: "H265",
|
|
456
|
+
family: "videotoolbox"
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
if (hardware.platform === "linux") {
|
|
460
|
+
if (hardware.gpu?.type === "nvidia") {
|
|
461
|
+
out.push({
|
|
462
|
+
encoder: "h264_nvenc",
|
|
463
|
+
codec: "H264",
|
|
464
|
+
family: "nvenc"
|
|
465
|
+
});
|
|
466
|
+
out.push({
|
|
467
|
+
encoder: "hevc_nvenc",
|
|
468
|
+
codec: "H265",
|
|
469
|
+
family: "nvenc"
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
if (hardware.gpu?.type === "intel" || hardware.gpu?.type === "amd" || !hardware.gpu) {
|
|
473
|
+
out.push({
|
|
474
|
+
encoder: "h264_vaapi",
|
|
475
|
+
codec: "H264",
|
|
476
|
+
family: "vaapi"
|
|
477
|
+
});
|
|
478
|
+
out.push({
|
|
479
|
+
encoder: "hevc_vaapi",
|
|
480
|
+
codec: "H265",
|
|
481
|
+
family: "vaapi"
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return out;
|
|
486
|
+
}
|
|
487
|
+
async function runTestEncode(candidate, opts) {
|
|
488
|
+
const ffmpeg = opts.ffmpegPath ?? "ffmpeg";
|
|
489
|
+
const timeout = opts.timeoutMs ?? 8e3;
|
|
490
|
+
const nullSink = os.platform() === "win32" ? "NUL" : "/dev/null";
|
|
491
|
+
const baseArgs = [
|
|
492
|
+
"-hide_banner",
|
|
493
|
+
"-loglevel",
|
|
494
|
+
"error"
|
|
495
|
+
];
|
|
496
|
+
const inArgs = [
|
|
497
|
+
"-f",
|
|
498
|
+
"lavfi",
|
|
499
|
+
"-i",
|
|
500
|
+
"testsrc=duration=0.04:size=16x16:rate=25"
|
|
501
|
+
];
|
|
502
|
+
let outArgs;
|
|
503
|
+
if (candidate.family === "vaapi") outArgs = [
|
|
504
|
+
"-vaapi_device",
|
|
505
|
+
"/dev/dri/renderD128",
|
|
506
|
+
"-vf",
|
|
507
|
+
"format=nv12,hwupload",
|
|
508
|
+
"-c:v",
|
|
509
|
+
candidate.encoder,
|
|
510
|
+
"-frames:v",
|
|
511
|
+
"1",
|
|
512
|
+
"-f",
|
|
513
|
+
"null",
|
|
514
|
+
nullSink
|
|
515
|
+
];
|
|
516
|
+
else outArgs = [
|
|
517
|
+
"-c:v",
|
|
518
|
+
candidate.encoder,
|
|
519
|
+
"-frames:v",
|
|
520
|
+
"1",
|
|
521
|
+
"-f",
|
|
522
|
+
"null",
|
|
523
|
+
nullSink
|
|
524
|
+
];
|
|
525
|
+
try {
|
|
526
|
+
await execFileAsync$1(ffmpeg, [
|
|
527
|
+
...baseArgs,
|
|
528
|
+
...inArgs,
|
|
529
|
+
...outArgs
|
|
530
|
+
], {
|
|
531
|
+
timeout,
|
|
532
|
+
encoding: "utf8"
|
|
533
|
+
});
|
|
534
|
+
return {
|
|
535
|
+
encoder: candidate.encoder,
|
|
536
|
+
codec: candidate.codec,
|
|
537
|
+
family: candidate.family,
|
|
538
|
+
available: true
|
|
539
|
+
};
|
|
540
|
+
} catch (err) {
|
|
541
|
+
return {
|
|
542
|
+
encoder: candidate.encoder,
|
|
543
|
+
codec: candidate.codec,
|
|
544
|
+
family: candidate.family,
|
|
545
|
+
available: false,
|
|
546
|
+
reason: errMsg(err)
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
var HardwareEncoderProber = class {
|
|
551
|
+
cached = null;
|
|
552
|
+
inflight = null;
|
|
553
|
+
logger;
|
|
554
|
+
ffmpegPath;
|
|
555
|
+
constructor(logger, ffmpegPath = "ffmpeg") {
|
|
556
|
+
this.logger = logger;
|
|
557
|
+
this.ffmpegPath = ffmpegPath;
|
|
558
|
+
}
|
|
559
|
+
getCached() {
|
|
560
|
+
return this.cached;
|
|
561
|
+
}
|
|
562
|
+
async probe(hardware, options = {}) {
|
|
563
|
+
if (!options.force && this.cached) return this.cached;
|
|
564
|
+
if (this.inflight) return this.inflight;
|
|
565
|
+
this.inflight = this.runProbe(hardware).then((res) => {
|
|
566
|
+
this.cached = res;
|
|
567
|
+
this.inflight = null;
|
|
568
|
+
return res;
|
|
569
|
+
}).catch((err) => {
|
|
570
|
+
this.inflight = null;
|
|
571
|
+
throw err;
|
|
572
|
+
});
|
|
573
|
+
return this.inflight;
|
|
574
|
+
}
|
|
575
|
+
async runProbe(hardware) {
|
|
576
|
+
const candidates = buildCandidates(hardware);
|
|
577
|
+
const start = Date.now();
|
|
578
|
+
this.logger.info("Probing hardware encoders", { meta: {
|
|
579
|
+
platform: hardware.platform,
|
|
580
|
+
arch: hardware.arch,
|
|
581
|
+
candidates: candidates.length
|
|
582
|
+
} });
|
|
583
|
+
const probes = await Promise.all(candidates.map((c) => runTestEncode(c, { ffmpegPath: this.ffmpegPath })));
|
|
584
|
+
probes.push({
|
|
585
|
+
encoder: "libx264",
|
|
586
|
+
codec: "H264",
|
|
587
|
+
family: "software",
|
|
588
|
+
available: true
|
|
589
|
+
});
|
|
590
|
+
probes.push({
|
|
591
|
+
encoder: "libx265",
|
|
592
|
+
codec: "H265",
|
|
593
|
+
family: "software",
|
|
594
|
+
available: true
|
|
595
|
+
});
|
|
596
|
+
const pickDefault = (codec) => {
|
|
597
|
+
const hw = probes.find((p) => p.codec === codec && p.available && p.family !== "software");
|
|
598
|
+
if (hw) return hw.encoder;
|
|
599
|
+
return codec === "H264" ? "libx264" : "libx265";
|
|
600
|
+
};
|
|
601
|
+
const result = {
|
|
602
|
+
encoders: probes,
|
|
603
|
+
defaultH264: pickDefault("H264"),
|
|
604
|
+
defaultH265: pickDefault("H265"),
|
|
605
|
+
probedAt: Date.now()
|
|
606
|
+
};
|
|
607
|
+
const elapsed = Date.now() - start;
|
|
608
|
+
this.logger.info("Hardware encoder probe complete", { meta: {
|
|
609
|
+
elapsedMs: elapsed,
|
|
610
|
+
defaultH264: result.defaultH264,
|
|
611
|
+
defaultH265: result.defaultH265,
|
|
612
|
+
availableHw: probes.filter((p) => p.available && p.family !== "software").map((p) => p.encoder)
|
|
613
|
+
} });
|
|
614
|
+
return result;
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
//#endregion
|
|
618
|
+
//#region src/builtins/platform-probe/hardware-decode-accel-probe.ts
|
|
619
|
+
var execFileAsync = promisify(execFile);
|
|
620
|
+
var defaultExecFn = async (ffmpegPath, args) => {
|
|
621
|
+
const { stdout } = await execFileAsync(ffmpegPath, [...args], {
|
|
622
|
+
timeout: 8e3,
|
|
623
|
+
encoding: "utf8"
|
|
624
|
+
});
|
|
625
|
+
return { stdout };
|
|
626
|
+
};
|
|
627
|
+
/**
|
|
628
|
+
* Parse the output of `ffmpeg -hwaccels`. The listing looks like:
|
|
629
|
+
*
|
|
630
|
+
* Hardware acceleration methods:
|
|
631
|
+
* videotoolbox
|
|
632
|
+
*
|
|
633
|
+
* The header line is dropped; remaining non-empty trimmed lines are the
|
|
634
|
+
* method names, lowercased and de-duplicated (order preserved).
|
|
635
|
+
*/
|
|
636
|
+
function parseHwAccelsOutput(stdout) {
|
|
637
|
+
const seen = /* @__PURE__ */ new Set();
|
|
638
|
+
const methods = [];
|
|
639
|
+
for (const raw of stdout.split("\n")) {
|
|
640
|
+
const line = raw.trim().toLowerCase();
|
|
641
|
+
if (line.length === 0) continue;
|
|
642
|
+
if (line.endsWith("methods:")) continue;
|
|
643
|
+
if (seen.has(line)) continue;
|
|
644
|
+
seen.add(line);
|
|
645
|
+
methods.push(line);
|
|
646
|
+
}
|
|
647
|
+
return methods;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Probes which `-hwaccel` decode backends the configured ffmpeg binary
|
|
651
|
+
* supports. Mirrors {@link HardwareEncoderProber} (configured binary, cached,
|
|
652
|
+
* single-flight, forceable). Never throws: a probe failure yields an empty
|
|
653
|
+
* method list so callers fall back to software decode.
|
|
654
|
+
*/
|
|
655
|
+
var HardwareDecodeAccelProber = class {
|
|
656
|
+
cached = null;
|
|
657
|
+
inflight = null;
|
|
658
|
+
logger;
|
|
659
|
+
ffmpegPath;
|
|
660
|
+
execFn;
|
|
661
|
+
constructor(logger, ffmpegPath = "ffmpeg", execFn = defaultExecFn) {
|
|
662
|
+
this.logger = logger;
|
|
663
|
+
this.ffmpegPath = ffmpegPath;
|
|
664
|
+
this.execFn = execFn;
|
|
665
|
+
}
|
|
666
|
+
getCached() {
|
|
667
|
+
return this.cached;
|
|
668
|
+
}
|
|
669
|
+
async probe(options = {}) {
|
|
670
|
+
if (!options.force && this.cached) return this.cached;
|
|
671
|
+
if (this.inflight) return this.inflight;
|
|
672
|
+
this.inflight = this.runProbe().then((res) => {
|
|
673
|
+
this.cached = res;
|
|
674
|
+
this.inflight = null;
|
|
675
|
+
return res;
|
|
676
|
+
}).catch((err) => {
|
|
677
|
+
this.inflight = null;
|
|
678
|
+
throw err;
|
|
679
|
+
});
|
|
680
|
+
return this.inflight;
|
|
681
|
+
}
|
|
682
|
+
async runProbe() {
|
|
683
|
+
try {
|
|
684
|
+
const { stdout } = await this.execFn(this.ffmpegPath, ["-hide_banner", "-hwaccels"]);
|
|
685
|
+
const methods = parseHwAccelsOutput(stdout);
|
|
686
|
+
this.logger.info("Hardware decode-accel probe complete", { meta: { methods } });
|
|
687
|
+
return {
|
|
688
|
+
methods,
|
|
689
|
+
probedAt: Date.now()
|
|
690
|
+
};
|
|
691
|
+
} catch (err) {
|
|
692
|
+
this.logger.warn("Hardware decode-accel probe failed — assuming none available", { meta: { error: errMsg(err) } });
|
|
693
|
+
return {
|
|
694
|
+
methods: [],
|
|
695
|
+
probedAt: Date.now()
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
//#endregion
|
|
701
|
+
//#region src/builtins/platform-probe/index.ts
|
|
702
|
+
var PlatformProbeNativeAddon = class extends BaseAddon {
|
|
703
|
+
scorer = null;
|
|
704
|
+
encoderProber = null;
|
|
705
|
+
decodeAccelProber = null;
|
|
706
|
+
cachedCaps = null;
|
|
707
|
+
constructor() {
|
|
708
|
+
super({});
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Resolve the ffmpeg binary the encoder probe should test, from the cluster
|
|
712
|
+
* `ffmpeg` config section (`binaryPath`). The probe MUST exercise the same
|
|
713
|
+
* binary the broker/recorder spawn, or it may report encoders for a different
|
|
714
|
+
* ffmpeg. Defaults to PATH `ffmpeg` on any miss.
|
|
715
|
+
*/
|
|
716
|
+
async resolveFfmpegBinaryPath() {
|
|
717
|
+
try {
|
|
718
|
+
const bp = (await this.ctx.settings?.getSection("ffmpeg") ?? {})["binaryPath"];
|
|
719
|
+
return typeof bp === "string" && bp.trim().length > 0 ? bp : "ffmpeg";
|
|
720
|
+
} catch {
|
|
721
|
+
return "ffmpeg";
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
async onInitialize() {
|
|
725
|
+
const embeddedPython = await this.ctx.deps.ensurePython().catch((err) => {
|
|
726
|
+
this.ctx.logger.debug("ensurePython unavailable for platform probe", { meta: { error: errMsg(err) } });
|
|
727
|
+
return null;
|
|
728
|
+
});
|
|
729
|
+
this.scorer = new PlatformScorer(this.ctx.logger, embeddedPython);
|
|
730
|
+
const ffmpegPath = await this.resolveFfmpegBinaryPath();
|
|
731
|
+
this.encoderProber = new HardwareEncoderProber(this.ctx.logger, ffmpegPath);
|
|
732
|
+
this.decodeAccelProber = new HardwareDecodeAccelProber(this.ctx.logger, ffmpegPath);
|
|
733
|
+
const emitPhase = (phase, payload) => {
|
|
734
|
+
this.ctx.eventBus?.emit({
|
|
735
|
+
id: `platform-probe-${phase}-${Date.now()}`,
|
|
736
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
737
|
+
source: {
|
|
738
|
+
type: "core",
|
|
739
|
+
id: "platform-probe-native"
|
|
740
|
+
},
|
|
741
|
+
category: EventCategory.PlatformProbePhase,
|
|
742
|
+
data: {
|
|
743
|
+
type: "platform-probe.phase",
|
|
744
|
+
phase,
|
|
745
|
+
...payload
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
};
|
|
749
|
+
const probePromise = this.scorer.probe(emitPhase).then((caps) => {
|
|
750
|
+
this.cachedCaps = caps;
|
|
751
|
+
this.ctx.logger.info("Platform probe complete", { meta: {
|
|
752
|
+
cpuModel: caps.hardware.cpuModel,
|
|
753
|
+
arch: caps.hardware.arch,
|
|
754
|
+
totalRAM_MB: caps.hardware.totalRAM_MB,
|
|
755
|
+
gpu: caps.hardware.gpu?.name ?? "none",
|
|
756
|
+
bestReason: caps.bestScore.reason,
|
|
757
|
+
bestScore: caps.bestScore.score
|
|
758
|
+
} });
|
|
759
|
+
return caps;
|
|
760
|
+
}).catch((err) => {
|
|
761
|
+
const msg = errMsg(err);
|
|
762
|
+
this.ctx.logger.error("Platform probe failed", { meta: { error: msg } });
|
|
763
|
+
emitPhase("error", { message: msg });
|
|
764
|
+
throw err;
|
|
765
|
+
});
|
|
766
|
+
const getCaps = async () => {
|
|
767
|
+
return this.cachedCaps ?? probePromise;
|
|
768
|
+
};
|
|
769
|
+
return [{
|
|
770
|
+
capability: platformProbeCapability,
|
|
771
|
+
provider: {
|
|
772
|
+
getCapabilities: async () => {
|
|
773
|
+
return getCaps();
|
|
774
|
+
},
|
|
775
|
+
getHardware: async () => {
|
|
776
|
+
return (await getCaps()).hardware;
|
|
777
|
+
},
|
|
778
|
+
resolveInferenceConfig: async (input) => {
|
|
779
|
+
const caps = await getCaps();
|
|
780
|
+
return new InferenceConfigResolver(caps.scores, caps.hardware).resolve(input.requirements);
|
|
781
|
+
},
|
|
782
|
+
resolveHwAccel: async (input) => {
|
|
783
|
+
const hwaccel = this.ctx.kernel.hwaccel;
|
|
784
|
+
if (!hwaccel) return { preferred: [] };
|
|
785
|
+
return { preferred: (await hwaccel.resolve(input.prefer ?? null)).preferred };
|
|
786
|
+
},
|
|
787
|
+
getHardwareEncoders: async () => {
|
|
788
|
+
const prober = this.encoderProber;
|
|
789
|
+
if (!prober) throw new Error("Hardware encoder prober not initialized");
|
|
790
|
+
const cached = prober.getCached();
|
|
791
|
+
if (cached) return cached;
|
|
792
|
+
const caps = await getCaps();
|
|
793
|
+
return prober.probe(caps.hardware);
|
|
794
|
+
},
|
|
795
|
+
refreshHardwareEncoders: async () => {
|
|
796
|
+
const prober = this.encoderProber;
|
|
797
|
+
if (!prober) throw new Error("Hardware encoder prober not initialized");
|
|
798
|
+
const caps = await getCaps();
|
|
799
|
+
return prober.probe(caps.hardware, { force: true });
|
|
800
|
+
},
|
|
801
|
+
getHardwareDecodeAccels: async () => {
|
|
802
|
+
const prober = this.decodeAccelProber;
|
|
803
|
+
if (!prober) throw new Error("Hardware decode-accel prober not initialized");
|
|
804
|
+
return prober.probe();
|
|
805
|
+
},
|
|
806
|
+
refreshHardwareDecodeAccels: async () => {
|
|
807
|
+
const prober = this.decodeAccelProber;
|
|
808
|
+
if (!prober) throw new Error("Hardware decode-accel prober not initialized");
|
|
809
|
+
return prober.probe({ force: true });
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}];
|
|
813
|
+
}
|
|
814
|
+
async onShutdown() {
|
|
815
|
+
this.scorer = null;
|
|
816
|
+
this.encoderProber = null;
|
|
817
|
+
this.decodeAccelProber = null;
|
|
818
|
+
this.cachedCaps = null;
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
//#endregion
|
|
822
|
+
export { HardwareDecodeAccelProber, HardwareEncoderProber, InferenceConfigResolver, PlatformProbeNativeAddon, PlatformProbeNativeAddon as default, PlatformScorer };
|