@camstack/addon-pipeline 1.0.2 → 1.0.4

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.
@@ -3896,22 +3896,68 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
3896
3896
  async setApi(addonCtx) {
3897
3897
  this.addonCtx = addonCtx;
3898
3898
  }
3899
+ /**
3900
+ * Fetch platform-probe data for engine + device gating. Returns null for
3901
+ * both fields when the probe cap is not yet reachable (cold-start / probe
3902
+ * addon not installed), so callers fall back to the full static catalog.
3903
+ */
3904
+ async fetchProbeGatingData() {
3905
+ try {
3906
+ const api = this.addonCtx?.api;
3907
+ if (!api) return {
3908
+ availableBackends: null,
3909
+ hardware: null
3910
+ };
3911
+ const caps = await api.platformProbe.getCapabilities.query();
3912
+ if (!caps?.scores) return {
3913
+ availableBackends: null,
3914
+ hardware: null
3915
+ };
3916
+ return {
3917
+ availableBackends: caps.scores.filter((s) => s.runtime === "python" && s.available).map((s) => s.backend),
3918
+ hardware: caps.hardware ?? null
3919
+ };
3920
+ } catch {
3921
+ return {
3922
+ availableBackends: null,
3923
+ hardware: null
3924
+ };
3925
+ }
3926
+ }
3899
3927
  async getSchema(engine) {
3900
3928
  if (!engine || !engine.runtime) engine = await this.getSelectedEngine();
3901
3929
  const format = engine.format;
3902
3930
  const slots = buildSchemaSlots(format, this.modelsDir);
3931
+ const { availableBackends, hardware } = await this.fetchProbeGatingData();
3903
3932
  return {
3904
- availableEngines: this.getAvailableEnginesWithDevices(),
3933
+ availableEngines: this.getAvailableEnginesWithDevices(availableBackends, hardware),
3905
3934
  selectedEngine: { ...engine },
3906
3935
  slots
3907
3936
  };
3908
3937
  }
3909
3938
  async getAvailableEngines() {
3910
- return this.getAvailableEnginesWithDevices().map((e) => e.engine);
3939
+ const { availableBackends, hardware } = await this.fetchProbeGatingData();
3940
+ return this.getAvailableEnginesWithDevices(availableBackends, hardware).map((e) => e.engine);
3911
3941
  }
3912
- getAvailableEnginesWithDevices() {
3913
- const engines = [];
3914
- if (process.platform === "darwin") engines.push({
3942
+ /**
3943
+ * Build the engine catalog filtered by probe data.
3944
+ *
3945
+ * When `availableBackends` is not null, only engines whose backend is
3946
+ * reported as available by the platform-probe cap are included. This
3947
+ * prevents the benchmark picker from offering (e.g.) CoreML on an Intel
3948
+ * Linux host where the Python coremltools package is absent.
3949
+ *
3950
+ * When `hardware` is not null, the per-engine device list is narrowed to
3951
+ * entries that are valid on this host's hardware (no Intel NPU → no 'npu'
3952
+ * device for OpenVINO, no NVIDIA GPU → no 'cuda' for ONNX, etc.).
3953
+ *
3954
+ * When either argument is null (probe unreachable / cold-start), the full
3955
+ * static catalog is returned unchanged — conservative fallback so the UI
3956
+ * still renders all options rather than going blank.
3957
+ */
3958
+ getAvailableEnginesWithDevices(availableBackends, hardware) {
3959
+ const allEngines = [];
3960
+ if (process.platform === "darwin") allEngines.push({
3915
3961
  engine: {
3916
3962
  runtime: "python",
3917
3963
  backend: "coreml",
@@ -3920,7 +3966,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
3920
3966
  devices: COREML_DEVICES,
3921
3967
  defaultDevice: "all"
3922
3968
  });
3923
- engines.push({
3969
+ allEngines.push({
3924
3970
  engine: {
3925
3971
  runtime: "python",
3926
3972
  backend: "openvino",
@@ -3929,7 +3975,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
3929
3975
  devices: OPENVINO_DEVICES,
3930
3976
  defaultDevice: "auto"
3931
3977
  });
3932
- engines.push({
3978
+ allEngines.push({
3933
3979
  engine: {
3934
3980
  runtime: "python",
3935
3981
  backend: "onnx",
@@ -3938,7 +3984,19 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
3938
3984
  devices: ONNX_PYTHON_DEVICES,
3939
3985
  defaultDevice: "cpu"
3940
3986
  });
3941
- return engines;
3987
+ if (!availableBackends) return allEngines;
3988
+ return allEngines.filter((e) => availableBackends.includes(e.engine.backend)).map((e) => {
3989
+ if (!hardware) return e;
3990
+ const filtered = filterDeviceOptionsByHardware(e.devices.map((d) => ({
3991
+ value: d.id,
3992
+ label: d.label
3993
+ })), e.engine.backend, hardware);
3994
+ const allowedIds = new Set(filtered.map((o) => o.value));
3995
+ return {
3996
+ ...e,
3997
+ devices: e.devices.filter((d) => allowedIds.has(d.id))
3998
+ };
3999
+ });
3942
4000
  }
3943
4001
  async getDefaultSteps(engine) {
3944
4002
  return buildDefaultStepTree(engine.format);
@@ -4030,7 +4088,8 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4030
4088
  };
4031
4089
  })
4032
4090
  }));
4033
- const engines = this.getAvailableEnginesWithDevices();
4091
+ const { availableBackends, hardware } = await this.fetchProbeGatingData();
4092
+ const engines = this.getAvailableEnginesWithDevices(availableBackends, hardware);
4034
4093
  const nodeBackends = [];
4035
4094
  const pythonBackends = [];
4036
4095
  for (const e of engines) if (e.engine.runtime === "node") nodeBackends.push({
@@ -5131,19 +5190,20 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
5131
5190
  */
5132
5191
  async loadEngine() {
5133
5192
  const store = await this.readStore();
5134
- const runtime = store["engineRuntime"];
5135
- const backend = store["engineBackend"];
5136
- if (typeof runtime === "string" && typeof backend === "string" && runtime && backend) {
5193
+ const storedRuntime = store["engineRuntime"];
5194
+ const storedBackend = store["engineBackend"];
5195
+ if (typeof storedBackend === "string" && storedBackend.length > 0) {
5196
+ const backend = storedBackend;
5137
5197
  const storedDevice = typeof store["engineDevice"] === "string" ? String(store["engineDevice"]) : "";
5138
5198
  const detected = DetectionPipelineProvider.detectBestEngine();
5139
- if (runtime === "python" && !DetectionPipelineProvider.isPythonBackendAvailable(backend, this.executorOptions.pythonPath ?? "")) {
5199
+ const migratedBackend = typeof storedRuntime === "string" && storedRuntime === "node" && backend === "cpu" ? "onnx" : backend;
5200
+ if (!DetectionPipelineProvider.isPythonBackendAvailable(migratedBackend, this.executorOptions.pythonPath ?? "")) {
5140
5201
  this.log.warn("Stored engine backend unavailable on this node — falling back to detected best", { meta: {
5141
- stored: `${runtime}/${backend}`,
5142
- fallback: `${detected.runtime}/${detected.backend}`
5202
+ stored: migratedBackend,
5203
+ fallback: `${detected.backend}`
5143
5204
  } });
5144
5205
  return detected;
5145
5206
  }
5146
- const migratedBackend = runtime === "node" && backend === "cpu" ? "onnx" : backend;
5147
5207
  const device = storedDevice || detected.device;
5148
5208
  return {
5149
5209
  runtime: "python",
@@ -5313,11 +5373,42 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
5313
5373
  return { success: true };
5314
5374
  }
5315
5375
  async reprobeEngine() {
5316
- const best = DetectionPipelineProvider.detectBestEngine();
5317
- const probedLabel = `${best.runtime}/${best.backend}/${best.device ?? "default"}`;
5318
- await this.writeStore({ probedBestEngine: probedLabel });
5319
- this.log.info("Re-probed engine", { meta: {
5320
- runtime: best.runtime,
5376
+ const api = this.addonCtx?.api;
5377
+ let best;
5378
+ if (api) try {
5379
+ const caps = await api.platformProbe.getCapabilities.query();
5380
+ const bs = caps?.bestScore;
5381
+ if (bs && bs.runtime === "python") {
5382
+ const probeBackend = bs.backend;
5383
+ const probeDevice = (() => {
5384
+ const hw = caps.hardware;
5385
+ if (probeBackend === "coreml") return hw?.npu?.type === "apple-ane" ? "ane" : "all";
5386
+ if (probeBackend === "openvino") {
5387
+ if (hw?.npu?.type === "intel-npu") return "npu";
5388
+ if (hw?.gpu?.type === "intel") return "gpu";
5389
+ return "auto";
5390
+ }
5391
+ if (probeBackend === "onnx") return hw?.gpu?.type === "nvidia" ? "cuda" : "cpu";
5392
+ return "cpu";
5393
+ })();
5394
+ best = {
5395
+ runtime: "python",
5396
+ backend: probeBackend,
5397
+ format: backendToFormat(probeBackend),
5398
+ device: probeDevice
5399
+ };
5400
+ } else best = DetectionPipelineProvider.detectBestEngine();
5401
+ } catch {
5402
+ best = DetectionPipelineProvider.detectBestEngine();
5403
+ }
5404
+ else best = DetectionPipelineProvider.detectBestEngine();
5405
+ const probedLabel = `${best.backend}/${best.device ?? "default"}`;
5406
+ await this.writeStore({
5407
+ probedBestEngine: probedLabel,
5408
+ engineBackend: best.backend,
5409
+ engineDevice: best.device ?? "cpu"
5410
+ });
5411
+ this.log.info("Re-probed engine — wrote back engineBackend + engineDevice", { meta: {
5321
5412
  backend: best.backend,
5322
5413
  device: best.device ?? null,
5323
5414
  probedBestEngine: probedLabel
@@ -5735,10 +5826,12 @@ function shouldInstallOpenvino(hardware) {
5735
5826
  const hasIntelNpu = hardware.npu?.type === "intel-npu";
5736
5827
  return hasIntelGpu || hasIntelNpu;
5737
5828
  }
5738
- var RUNTIMES = [{
5739
- value: "python",
5740
- label: "Python (CoreML / OpenVINO / ONNX Runtime)"
5741
- }];
5829
+ /**
5830
+ * Full catalog of execution providers. The settings UI only shows the subset
5831
+ * reported as available by the platform-probe cap (`getGlobalSettings` gates
5832
+ * options by probe scores). Kept as the static universe so the static schema
5833
+ * (returned before a live ctx is available) still lists all fields.
5834
+ */
5742
5835
  var BACKENDS_BY_RUNTIME = { python: [
5743
5836
  {
5744
5837
  value: "coreml",
@@ -5809,6 +5902,53 @@ var DEVICES_BY_BACKEND = {
5809
5902
  label: "CPU"
5810
5903
  }]
5811
5904
  };
5905
+ /**
5906
+ * Filter the per-backend device option list by what the platform probe
5907
+ * reports as available hardware on this host. Rules (derived from the
5908
+ * confirmed target model):
5909
+ *
5910
+ * coreml:
5911
+ * - `ane` only when `hardware.npu?.type === 'apple-ane'`
5912
+ * - `gpu` and `all` always shown (CPU+GPU present on every Mac)
5913
+ * - `cpu` always shown
5914
+ *
5915
+ * openvino:
5916
+ * - `npu` only when `hardware.npu?.type === 'intel-npu'`
5917
+ * - `gpu` only when `hardware.gpu?.type === 'intel'`
5918
+ * - `auto` and `cpu` always shown
5919
+ *
5920
+ * onnx:
5921
+ * - `cuda` only when `hardware.gpu?.type === 'nvidia'`
5922
+ * - `coreml` only when `hardware.npu?.type === 'apple-ane'` (CoreML EP)
5923
+ * - `cpu` always shown
5924
+ *
5925
+ * When `hardware` is null (probe unreachable), the full catalog is returned
5926
+ * unchanged so the UI still renders all options.
5927
+ */
5928
+ function filterDeviceOptionsByHardware(options, backend, hardware) {
5929
+ if (!hardware) return options;
5930
+ const hasAppleAne = hardware.npu?.type === "apple-ane";
5931
+ const hasIntelNpu = hardware.npu?.type === "intel-npu";
5932
+ const hasIntelGpu = hardware.gpu?.type === "intel";
5933
+ const hasNvidiaGpu = hardware.gpu?.type === "nvidia";
5934
+ switch (backend) {
5935
+ case "coreml": return options.filter((o) => {
5936
+ if (o.value === "ane") return hasAppleAne;
5937
+ return true;
5938
+ });
5939
+ case "openvino": return options.filter((o) => {
5940
+ if (o.value === "npu") return hasIntelNpu;
5941
+ if (o.value === "gpu") return hasIntelGpu;
5942
+ return true;
5943
+ });
5944
+ case "onnx": return options.filter((o) => {
5945
+ if (o.value === "cuda") return hasNvidiaGpu;
5946
+ if (o.value === "coreml") return hasAppleAne;
5947
+ return true;
5948
+ });
5949
+ default: return options;
5950
+ }
5951
+ }
5812
5952
  var BACKEND_TO_FORMAT = {
5813
5953
  cpu: "onnx",
5814
5954
  coreml: "coreml",
@@ -5940,13 +6080,13 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
5940
6080
  title: "Object detection",
5941
6081
  tab: "engine",
5942
6082
  order: 0,
5943
- description: "Vision-detection backend (object detection, face detection, plate OCR). Chain: runtime backend hardware. Backend options depend on runtime; hardware options depend on backend. All three restart the addon on change.",
6083
+ description: "Vision-detection execution provider (object detection, face detection, plate OCR). Inference always runs via the embedded Python runtime. Provider and hardware options are gated by what the platform probe reports as available on this host. Changes restart the addon.",
5944
6084
  fields: [
5945
6085
  this.field({
5946
6086
  type: "text",
5947
6087
  key: "probedBestEngine",
5948
6088
  label: "Probed best",
5949
- description: "Auto-detected best engine for this host (format: runtime/backend/device). Click the refresh icon to re-run the probe — the detected values get written back into the three fields below.",
6089
+ description: "Auto-detected best engine for this host (format: provider/device, e.g. \"openvino/npu\"). Click the refresh icon to re-run the probe — the detected provider and hardware device are written back into the two fields below, overwriting any manual override.",
5950
6090
  readonlyField: true,
5951
6091
  default: "",
5952
6092
  actions: [{
@@ -5955,19 +6095,10 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
5955
6095
  tooltip: "Re-probe engine"
5956
6096
  }]
5957
6097
  }),
5958
- this.field({
5959
- type: "select",
5960
- key: "engineRuntime",
5961
- label: "Runtime",
5962
- options: [...RUNTIMES],
5963
- default: DEFAULT_CONFIG.engineRuntime,
5964
- immediate: true,
5965
- requiresRestart: true
5966
- }),
5967
6098
  this.field({
5968
6099
  type: "select",
5969
6100
  key: "engineBackend",
5970
- label: "Backend",
6101
+ label: "Execution provider",
5971
6102
  options: [...BACKENDS_BY_RUNTIME.python],
5972
6103
  default: DEFAULT_CONFIG.engineBackend,
5973
6104
  immediate: true,
@@ -6105,14 +6236,16 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
6105
6236
  ...overlay
6106
6237
  } : stored;
6107
6238
  const runtime = "python";
6108
- const availableBackends = await this.resolveAvailableBackends(ctx, runtime);
6239
+ const probeResult = await this.resolveProbeData(ctx, runtime);
6240
+ const availableBackends = probeResult.availableBackends;
6241
+ const hardware = probeResult.hardware;
6109
6242
  const runtimeBackends = availableBackends.length > 0 ? BACKENDS_BY_RUNTIME[runtime].filter((b) => availableBackends.includes(b.value)) : BACKENDS_BY_RUNTIME[runtime];
6110
6243
  const storedBackend = typeof merged.engineBackend === "string" ? merged.engineBackend : "";
6111
- const backend = runtimeBackends.find((b) => b.value === storedBackend)?.value ?? runtimeBackends[0]?.value ?? "coreml";
6112
- const deviceOptions = DEVICES_BY_BACKEND[backend] ?? [{
6244
+ const backend = runtimeBackends.find((b) => b.value === storedBackend)?.value ?? probeResult.bestBackend ?? runtimeBackends[0]?.value ?? "coreml";
6245
+ const deviceOptions = filterDeviceOptionsByHardware(DEVICES_BY_BACKEND[backend] ?? [{
6113
6246
  value: "cpu",
6114
6247
  label: "CPU"
6115
- }];
6248
+ }], backend, hardware);
6116
6249
  const storedDevice = typeof merged.engineDevice === "string" ? merged.engineDevice : "";
6117
6250
  const device = deviceOptions.find((d) => d.value === storedDevice)?.value ?? deviceOptions[0]?.value ?? "cpu";
6118
6251
  const raw = {
@@ -6162,28 +6295,47 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
6162
6295
  }, raw);
6163
6296
  }
6164
6297
  /**
6165
- * Ask the platform-probe cap which backends are actually installable
6166
- * on this node (checks for `openvino`, `coremltools`, `onnxruntime`
6167
- * Python modules + hardware presence). Returns a subset of the static
6168
- * catalog; an empty list signals the probe wasn't available so the
6169
- * caller should fall back to the full catalog rather than hiding
6170
- * every option.
6298
+ * Fetch the platform-probe capabilities and return:
6299
+ * - `availableBackends`: backends the probe reports as available for `runtime`
6300
+ * (empty caller falls back to full catalog).
6301
+ * - `hardware`: the probed hardware info (null when probe not reachable).
6302
+ * - `bestBackend`: the backend from the probe's `bestScore` (null when unavailable).
6303
+ *
6304
+ * A single probe call is made so both backend AND device gating use the
6305
+ * same snapshot without doubling the cap round-trip.
6171
6306
  */
6172
- async resolveAvailableBackends(ctx, runtime) {
6307
+ async resolveProbeData(ctx, runtime) {
6173
6308
  try {
6174
6309
  const api = ctx?.api;
6175
- if (!api) return [];
6310
+ if (!api) return {
6311
+ availableBackends: [],
6312
+ hardware: null,
6313
+ bestBackend: null
6314
+ };
6176
6315
  const caps = await api.platformProbe.getCapabilities.query();
6177
- if (!caps?.scores) return [];
6316
+ if (!caps?.scores) return {
6317
+ availableBackends: [],
6318
+ hardware: null,
6319
+ bestBackend: null
6320
+ };
6178
6321
  const out = /* @__PURE__ */ new Set();
6179
6322
  for (const s of caps.scores) {
6180
6323
  if (s.runtime !== runtime) continue;
6181
6324
  if (!s.available) continue;
6182
6325
  out.add(s.backend);
6183
6326
  }
6184
- return [...out];
6327
+ const bestBackend = caps.bestScore?.runtime === runtime ? caps.bestScore.backend ?? null : null;
6328
+ return {
6329
+ availableBackends: [...out],
6330
+ hardware: caps.hardware ?? null,
6331
+ bestBackend
6332
+ };
6185
6333
  } catch {
6186
- return [];
6334
+ return {
6335
+ availableBackends: [],
6336
+ hardware: null,
6337
+ bestBackend: null
6338
+ };
6187
6339
  }
6188
6340
  }
6189
6341
  /**
@@ -6408,6 +6560,7 @@ exports.ALL_STEPS = ALL_STEPS;
6408
6560
  exports.DetectionPipelineProvider = DetectionPipelineProvider;
6409
6561
  exports.backendToFormat = backendToFormat;
6410
6562
  exports.default = DetectionPipelineAddon;
6563
+ exports.filterDeviceOptionsByHardware = filterDeviceOptionsByHardware;
6411
6564
  exports.getDefaultModelForFormat = getDefaultModelForFormat;
6412
6565
  exports.getStepDefinition = getStepDefinition;
6413
6566
  exports.shouldInstallOpenvino = shouldInstallOpenvino;