@camstack/addon-ai 0.4.120 → 0.4.121

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.js CHANGED
@@ -5520,7 +5520,7 @@ function number(params) {
5520
5520
  return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
5521
5521
  }
5522
5522
  //#endregion
5523
- //#region ../types/dist/sleep-Dk-BdnBf.mjs
5523
+ //#region ../types/dist/sleep-Bs3xPtSh.mjs
5524
5524
  /**
5525
5525
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5526
5526
  * window to float samples (D455).
@@ -12024,6 +12024,24 @@ var GetStreamWithCodecInputSchema = object({
12024
12024
  });
12025
12025
  var RtpSourceSchema = object({
12026
12026
  url: string(),
12027
+ /**
12028
+ * The SENTRY counterpart of {@link url} — the dial that asks for the stream
12029
+ * WITHOUT asking for the camera — or `null` when this camera offers none.
12030
+ *
12031
+ * The broker answers it because the broker is the only thing that can:
12032
+ * eligibility is `sentryEligible` (a BATTERY camera that DECLARES the signals
12033
+ * cap), asked of facts only the broker holds. A consumer re-deriving it from
12034
+ * cap bindings would be a second authority, and two authorities on the same
12035
+ * question is how a composite camera ends up dialling a sleeping camera
12036
+ * because its own copy of the rule was a release behind.
12037
+ *
12038
+ * `null` is a real answer and is never a live url in disguise: a consumer
12039
+ * that silently fell back to {@link url} would perform exactly the wake this
12040
+ * field exists to prevent. Only a PASSTHROUGH acquisition can offer one — a
12041
+ * transcode hands back an egress the broker is feeding itself, which has no
12042
+ * sentry counterpart.
12043
+ */
12044
+ sentryUrl: string().nullable().optional(),
12027
12045
  videoCodec: _enum(["H264", "H265"]),
12028
12046
  audioCodec: string(),
12029
12047
  resolution: object({
@@ -27460,6 +27478,114 @@ DeviceType.Light, DeviceType.Siren, DeviceType.Switch, method(object({
27460
27478
  lastChangedAt: number$1()
27461
27479
  });
27462
27480
  /**
27481
+ * camera-grid-layout — the geometry of a COMPOSITE camera, on the camera's own
27482
+ * page.
27483
+ *
27484
+ * ## Why this is a capability and not an addon settings schema
27485
+ *
27486
+ * It was one, and it did not render. The addon declared the editor as a
27487
+ * `type: 'widget'` field inside its own `deviceSettingsSchema()`; the hub
27488
+ * returned that section correctly and `ConfigFormField` renders `type:'widget'`
27489
+ * perfectly well — and nothing ever asked camera-grid for it. The Cluster →
27490
+ * Pipeline → Device Overrides page interrogates a HAND-WRITTEN list of four
27491
+ * addons (`PIPELINE_CLUSTER_DEVICE_ADDONS`), whose own comment says an addon
27492
+ * not on it "falls off silently".
27493
+ *
27494
+ * Adding a fifth name to that list would have been the wrong fix twice over:
27495
+ * that page is per-camera DETECTION tuning, and a grid's geometry belongs
27496
+ * beside PTZ and motion zones on the camera itself — which is exactly where the
27497
+ * framework already puts a widget, when the widget is declared on a CAPABILITY.
27498
+ * `deviceConfig.ui` is the mechanism (`motion-zones`, `ptz`, `ptz-autotrack`
27499
+ * all use it): the framework emits the structural section and the widget
27500
+ * self-persists through the cap's own mutations.
27501
+ *
27502
+ * ## Why one addon may implement it
27503
+ *
27504
+ * It is a device-scoped NATIVE cap, registered by the grid camera device
27505
+ * itself. Nothing else declares a composite camera, so nothing else has a
27506
+ * layout — and the device-scoped route means the widget asks THE camera, not
27507
+ * "the camera-grid addon", which is what let the old custom-action pair be
27508
+ * reached only by a caller that already knew the addon id.
27509
+ *
27510
+ * ## The tab
27511
+ *
27512
+ * `streaming`, not a top-tab of its own. A grid's geometry IS what its stream
27513
+ * is, so the Streaming tab is where it belongs; a `grid` top-tab would need an
27514
+ * entry in `WELL_KNOWN_TAB_MAP` or the device page renders the raw id as the
27515
+ * label (measured on the robot camera, 2026-09-06 — a tab called "navigation"
27516
+ * next to "PTZ").
27517
+ */
27518
+ /** A rectangle in normalized [0,1] coordinates of whatever contains it. */
27519
+ var GridNormalizedRectSchema = object({
27520
+ x: number$1().min(0).max(1),
27521
+ y: number$1().min(0).max(1),
27522
+ width: number$1().gt(0).max(1),
27523
+ height: number$1().gt(0).max(1)
27524
+ });
27525
+ /**
27526
+ * One source camera, the part of its picture taken, and where that part lands.
27527
+ *
27528
+ * Both rectangles are NORMALIZED (D519): a source camera can change resolution
27529
+ * — a profile switch, a firmware update, a substream that comes back different
27530
+ * — and a stored PIXEL rectangle would quietly start cutting the wrong region,
27531
+ * which is the class of bug nobody files.
27532
+ */
27533
+ var GridLayoutCellSchema = object({
27534
+ deviceId: number$1().int().positive(),
27535
+ /** The part of the SOURCE taken, normalized against the source. */
27536
+ source: GridNormalizedRectSchema,
27537
+ /** Where it lands, normalized against the CANVAS. */
27538
+ cell: GridNormalizedRectSchema
27539
+ });
27540
+ /**
27541
+ * Which profiles this grid can actually compose, and why not.
27542
+ *
27543
+ * A grid's `high` composes its sources' `high` and its `low` their `low`, so a
27544
+ * profile is on offer only when EVERY source can serve it. The refusal NAMES
27545
+ * the sources, because "this grid has no low" is not a finding — "615 has no
27546
+ * low" is, and it is the one an operator can act on.
27547
+ */
27548
+ var GridProfileOfferSchema = object({
27549
+ profile: _enum([
27550
+ "high",
27551
+ "mid",
27552
+ "low"
27553
+ ]),
27554
+ offered: boolean(),
27555
+ /** Sources that cannot serve it. Empty when it is offered, or when there are no cells. */
27556
+ missingSources: array(number$1().int().positive())
27557
+ });
27558
+ var GridLayoutViewSchema = object({
27559
+ /** The persisted grid row this camera was declared from. */
27560
+ instanceId: string(),
27561
+ deviceId: number$1().int().nonnegative(),
27562
+ name: string(),
27563
+ /** The canvas the operator laid out — this grid's `high`. mid/low are scaled from it. */
27564
+ width: number$1().int(),
27565
+ height: number$1().int(),
27566
+ fps: number$1().int(),
27567
+ cells: array(GridLayoutCellSchema),
27568
+ /** What the catalog will publish, and what it refuses to. Read-only. */
27569
+ profiles: array(GridProfileOfferSchema)
27570
+ });
27571
+ var GridLayoutPatchSchema = object({
27572
+ deviceId: number$1().int().nonnegative(),
27573
+ name: string().min(1).max(160).optional(),
27574
+ width: number$1().int().min(160).max(7680).optional(),
27575
+ height: number$1().int().min(120).max(4320).optional(),
27576
+ fps: number$1().int().min(1).max(60).optional(),
27577
+ /**
27578
+ * The whole cell list at once. A per-cell patch would need an ordering the
27579
+ * editor does not have, and a half-applied layout is a picture nobody asked
27580
+ * for.
27581
+ */
27582
+ cells: array(GridLayoutCellSchema).max(16)
27583
+ });
27584
+ DeviceType.Camera, method(object({ deviceId: number$1().int().nonnegative() }), GridLayoutViewSchema.nullable(), { auth: "admin" }), method(GridLayoutPatchSchema, GridLayoutViewSchema, {
27585
+ kind: "mutation",
27586
+ auth: "admin"
27587
+ });
27588
+ /**
27463
27589
  * Motion-zones share the same MaskShape vocabulary as privacy-mask — the
27464
27590
  * on-camera motion-detection mask is a single `grid` region (a row-major
27465
27591
  * boolean cell lattice the camera's onboard VMD evaluates). Composing it as
@@ -32488,6 +32614,18 @@ Object.freeze({
32488
32614
  addonId: null,
32489
32615
  access: "view"
32490
32616
  },
32617
+ "cameraGridLayout.getLayout": {
32618
+ capName: "camera-grid-layout",
32619
+ capScope: "device",
32620
+ addonId: null,
32621
+ access: "view"
32622
+ },
32623
+ "cameraGridLayout.saveLayout": {
32624
+ capName: "camera-grid-layout",
32625
+ capScope: "device",
32626
+ addonId: null,
32627
+ access: "create"
32628
+ },
32491
32629
  "cameraStreams.getBrokerStreams": {
32492
32630
  capName: "camera-streams",
32493
32631
  capScope: "device",
@@ -38187,6 +38325,21 @@ Object.freeze({
38187
38325
  form: "single",
38188
38326
  optional: false
38189
38327
  }],
38328
+ "cameraGridLayout.getLayout": [{
38329
+ name: "deviceId",
38330
+ form: "single",
38331
+ optional: false
38332
+ }],
38333
+ "cameraGridLayout.saveLayout": [{
38334
+ name: "cells",
38335
+ form: "object-array",
38336
+ optional: false,
38337
+ itemField: "deviceId"
38338
+ }, {
38339
+ name: "deviceId",
38340
+ form: "single",
38341
+ optional: false
38342
+ }],
38190
38343
  "cameraStreams.getBrokerStreams": [{
38191
38344
  name: "deviceId",
38192
38345
  form: "single",
package/dist/addon.mjs CHANGED
@@ -5547,7 +5547,7 @@ function number(params) {
5547
5547
  return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
5548
5548
  }
5549
5549
  //#endregion
5550
- //#region ../types/dist/sleep-Dk-BdnBf.mjs
5550
+ //#region ../types/dist/sleep-Bs3xPtSh.mjs
5551
5551
  /**
5552
5552
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5553
5553
  * window to float samples (D455).
@@ -12051,6 +12051,24 @@ var GetStreamWithCodecInputSchema = object({
12051
12051
  });
12052
12052
  var RtpSourceSchema = object({
12053
12053
  url: string(),
12054
+ /**
12055
+ * The SENTRY counterpart of {@link url} — the dial that asks for the stream
12056
+ * WITHOUT asking for the camera — or `null` when this camera offers none.
12057
+ *
12058
+ * The broker answers it because the broker is the only thing that can:
12059
+ * eligibility is `sentryEligible` (a BATTERY camera that DECLARES the signals
12060
+ * cap), asked of facts only the broker holds. A consumer re-deriving it from
12061
+ * cap bindings would be a second authority, and two authorities on the same
12062
+ * question is how a composite camera ends up dialling a sleeping camera
12063
+ * because its own copy of the rule was a release behind.
12064
+ *
12065
+ * `null` is a real answer and is never a live url in disguise: a consumer
12066
+ * that silently fell back to {@link url} would perform exactly the wake this
12067
+ * field exists to prevent. Only a PASSTHROUGH acquisition can offer one — a
12068
+ * transcode hands back an egress the broker is feeding itself, which has no
12069
+ * sentry counterpart.
12070
+ */
12071
+ sentryUrl: string().nullable().optional(),
12054
12072
  videoCodec: _enum(["H264", "H265"]),
12055
12073
  audioCodec: string(),
12056
12074
  resolution: object({
@@ -27487,6 +27505,114 @@ DeviceType.Light, DeviceType.Siren, DeviceType.Switch, method(object({
27487
27505
  lastChangedAt: number$1()
27488
27506
  });
27489
27507
  /**
27508
+ * camera-grid-layout — the geometry of a COMPOSITE camera, on the camera's own
27509
+ * page.
27510
+ *
27511
+ * ## Why this is a capability and not an addon settings schema
27512
+ *
27513
+ * It was one, and it did not render. The addon declared the editor as a
27514
+ * `type: 'widget'` field inside its own `deviceSettingsSchema()`; the hub
27515
+ * returned that section correctly and `ConfigFormField` renders `type:'widget'`
27516
+ * perfectly well — and nothing ever asked camera-grid for it. The Cluster →
27517
+ * Pipeline → Device Overrides page interrogates a HAND-WRITTEN list of four
27518
+ * addons (`PIPELINE_CLUSTER_DEVICE_ADDONS`), whose own comment says an addon
27519
+ * not on it "falls off silently".
27520
+ *
27521
+ * Adding a fifth name to that list would have been the wrong fix twice over:
27522
+ * that page is per-camera DETECTION tuning, and a grid's geometry belongs
27523
+ * beside PTZ and motion zones on the camera itself — which is exactly where the
27524
+ * framework already puts a widget, when the widget is declared on a CAPABILITY.
27525
+ * `deviceConfig.ui` is the mechanism (`motion-zones`, `ptz`, `ptz-autotrack`
27526
+ * all use it): the framework emits the structural section and the widget
27527
+ * self-persists through the cap's own mutations.
27528
+ *
27529
+ * ## Why one addon may implement it
27530
+ *
27531
+ * It is a device-scoped NATIVE cap, registered by the grid camera device
27532
+ * itself. Nothing else declares a composite camera, so nothing else has a
27533
+ * layout — and the device-scoped route means the widget asks THE camera, not
27534
+ * "the camera-grid addon", which is what let the old custom-action pair be
27535
+ * reached only by a caller that already knew the addon id.
27536
+ *
27537
+ * ## The tab
27538
+ *
27539
+ * `streaming`, not a top-tab of its own. A grid's geometry IS what its stream
27540
+ * is, so the Streaming tab is where it belongs; a `grid` top-tab would need an
27541
+ * entry in `WELL_KNOWN_TAB_MAP` or the device page renders the raw id as the
27542
+ * label (measured on the robot camera, 2026-09-06 — a tab called "navigation"
27543
+ * next to "PTZ").
27544
+ */
27545
+ /** A rectangle in normalized [0,1] coordinates of whatever contains it. */
27546
+ var GridNormalizedRectSchema = object({
27547
+ x: number$1().min(0).max(1),
27548
+ y: number$1().min(0).max(1),
27549
+ width: number$1().gt(0).max(1),
27550
+ height: number$1().gt(0).max(1)
27551
+ });
27552
+ /**
27553
+ * One source camera, the part of its picture taken, and where that part lands.
27554
+ *
27555
+ * Both rectangles are NORMALIZED (D519): a source camera can change resolution
27556
+ * — a profile switch, a firmware update, a substream that comes back different
27557
+ * — and a stored PIXEL rectangle would quietly start cutting the wrong region,
27558
+ * which is the class of bug nobody files.
27559
+ */
27560
+ var GridLayoutCellSchema = object({
27561
+ deviceId: number$1().int().positive(),
27562
+ /** The part of the SOURCE taken, normalized against the source. */
27563
+ source: GridNormalizedRectSchema,
27564
+ /** Where it lands, normalized against the CANVAS. */
27565
+ cell: GridNormalizedRectSchema
27566
+ });
27567
+ /**
27568
+ * Which profiles this grid can actually compose, and why not.
27569
+ *
27570
+ * A grid's `high` composes its sources' `high` and its `low` their `low`, so a
27571
+ * profile is on offer only when EVERY source can serve it. The refusal NAMES
27572
+ * the sources, because "this grid has no low" is not a finding — "615 has no
27573
+ * low" is, and it is the one an operator can act on.
27574
+ */
27575
+ var GridProfileOfferSchema = object({
27576
+ profile: _enum([
27577
+ "high",
27578
+ "mid",
27579
+ "low"
27580
+ ]),
27581
+ offered: boolean(),
27582
+ /** Sources that cannot serve it. Empty when it is offered, or when there are no cells. */
27583
+ missingSources: array(number$1().int().positive())
27584
+ });
27585
+ var GridLayoutViewSchema = object({
27586
+ /** The persisted grid row this camera was declared from. */
27587
+ instanceId: string(),
27588
+ deviceId: number$1().int().nonnegative(),
27589
+ name: string(),
27590
+ /** The canvas the operator laid out — this grid's `high`. mid/low are scaled from it. */
27591
+ width: number$1().int(),
27592
+ height: number$1().int(),
27593
+ fps: number$1().int(),
27594
+ cells: array(GridLayoutCellSchema),
27595
+ /** What the catalog will publish, and what it refuses to. Read-only. */
27596
+ profiles: array(GridProfileOfferSchema)
27597
+ });
27598
+ var GridLayoutPatchSchema = object({
27599
+ deviceId: number$1().int().nonnegative(),
27600
+ name: string().min(1).max(160).optional(),
27601
+ width: number$1().int().min(160).max(7680).optional(),
27602
+ height: number$1().int().min(120).max(4320).optional(),
27603
+ fps: number$1().int().min(1).max(60).optional(),
27604
+ /**
27605
+ * The whole cell list at once. A per-cell patch would need an ordering the
27606
+ * editor does not have, and a half-applied layout is a picture nobody asked
27607
+ * for.
27608
+ */
27609
+ cells: array(GridLayoutCellSchema).max(16)
27610
+ });
27611
+ DeviceType.Camera, method(object({ deviceId: number$1().int().nonnegative() }), GridLayoutViewSchema.nullable(), { auth: "admin" }), method(GridLayoutPatchSchema, GridLayoutViewSchema, {
27612
+ kind: "mutation",
27613
+ auth: "admin"
27614
+ });
27615
+ /**
27490
27616
  * Motion-zones share the same MaskShape vocabulary as privacy-mask — the
27491
27617
  * on-camera motion-detection mask is a single `grid` region (a row-major
27492
27618
  * boolean cell lattice the camera's onboard VMD evaluates). Composing it as
@@ -32515,6 +32641,18 @@ Object.freeze({
32515
32641
  addonId: null,
32516
32642
  access: "view"
32517
32643
  },
32644
+ "cameraGridLayout.getLayout": {
32645
+ capName: "camera-grid-layout",
32646
+ capScope: "device",
32647
+ addonId: null,
32648
+ access: "view"
32649
+ },
32650
+ "cameraGridLayout.saveLayout": {
32651
+ capName: "camera-grid-layout",
32652
+ capScope: "device",
32653
+ addonId: null,
32654
+ access: "create"
32655
+ },
32518
32656
  "cameraStreams.getBrokerStreams": {
32519
32657
  capName: "camera-streams",
32520
32658
  capScope: "device",
@@ -38214,6 +38352,21 @@ Object.freeze({
38214
38352
  form: "single",
38215
38353
  optional: false
38216
38354
  }],
38355
+ "cameraGridLayout.getLayout": [{
38356
+ name: "deviceId",
38357
+ form: "single",
38358
+ optional: false
38359
+ }],
38360
+ "cameraGridLayout.saveLayout": [{
38361
+ name: "cells",
38362
+ form: "object-array",
38363
+ optional: false,
38364
+ itemField: "deviceId"
38365
+ }, {
38366
+ name: "deviceId",
38367
+ form: "single",
38368
+ optional: false
38369
+ }],
38217
38370
  "cameraStreams.getBrokerStreams": [{
38218
38371
  name: "deviceId",
38219
38372
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-ai",
3
- "version": "0.4.120",
3
+ "version": "0.4.121",
4
4
  "description": "AI addon for CamStack — the `llm` collection provider (cloud, LAN, and camstack-managed local llama.cpp profiles) plus the per-node `llm-runtime` managed executor.",
5
5
  "keywords": [
6
6
  "camstack",