@camstack/addon-ai 0.2.20 → 0.4.1

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
@@ -45,7 +45,7 @@ let node_fs_promises = require("node:fs/promises");
45
45
  node_fs_promises = __toESM(node_fs_promises);
46
46
  let node_child_process = require("node:child_process");
47
47
  let node_net = require("node:net");
48
- //#region ../types/dist/event-category-Cv9dO26A.mjs
48
+ //#region ../types/dist/event-category-Bxo5yJjt.mjs
49
49
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
50
50
  EventCategory["SystemBoot"] = "system.boot";
51
51
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -252,6 +252,33 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
252
252
  EventCategory["PipelineCameraAssigned"] = "pipeline.camera-assigned";
253
253
  EventCategory["PipelineCameraUnassigned"] = "pipeline.camera-unassigned";
254
254
  /**
255
+ * A node the orchestrator would otherwise place cameras on has NO usable
256
+ * inference device: the operator enabled one or more accelerators there and
257
+ * the live probe reports every one of them unavailable. Emitted once per
258
+ * TRANSITION into that state (never per dispatch), and the node is dropped
259
+ * from the placement candidate set for as long as it holds.
260
+ *
261
+ * This exists because the state was previously invisible: little-unraid
262
+ * absorbed 283k inference errors in a day while still being handed cameras,
263
+ * and nothing in the system said so.
264
+ *
265
+ * A node with no accelerators configured at all is NOT this — its devices
266
+ * are `disabled`, not `unavailable`, and the runner's default CPU pool
267
+ * serves it exactly as before.
268
+ */
269
+ EventCategory["PipelineNodeInferenceUnavailable"] = "pipeline.node-inference-unavailable";
270
+ /**
271
+ * A camera has an OPEN detection session and has produced no detection at
272
+ * all for longer than the blind threshold — the camera is being decoded and
273
+ * inferred and is returning nothing. Emitted once per transition into blind,
274
+ * per camera.
275
+ *
276
+ * The failure it reports: a 1h43 detection blackout on the entrance camera
277
+ * that nobody noticed, because "a camera that detects nothing" and "a quiet
278
+ * camera" produce byte-identical silence.
279
+ */
280
+ EventCategory["PipelineDetectionBlind"] = "pipeline.detection-blind";
281
+ /**
255
282
  * Per-camera pipeline config was mutated by the orchestrator
256
283
  * (3-level settings change via `setAgentAddonDefaults` /
257
284
  * `setCameraStepToggle` / `setCameraPipelineForAgent` or a
@@ -3043,6 +3070,9 @@ function handlePipeResult(left, next, ctx) {
3043
3070
  fallback: left.fallback
3044
3071
  }, ctx);
3045
3072
  }
3073
+ var $ZodPreprocess = /*@__PURE__*/ $constructor("$ZodPreprocess", (inst, def) => {
3074
+ $ZodPipe.init(inst, def);
3075
+ });
3046
3076
  var $ZodReadonly = /*@__PURE__*/ $constructor("$ZodReadonly", (inst, def) => {
3047
3077
  $ZodType.init(inst, def);
3048
3078
  defineLazy(inst._zod, "propValues", () => def.innerType._zod.propValues);
@@ -4360,7 +4390,7 @@ var parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError);
4360
4390
  var safeParse = /* @__PURE__ */ _safeParse(ZodRealError);
4361
4391
  var safeParseAsync = /* @__PURE__ */ _safeParseAsync(ZodRealError);
4362
4392
  var encode = /* @__PURE__ */ _encode(ZodRealError);
4363
- var decode = /* @__PURE__ */ _decode(ZodRealError);
4393
+ var decode$1 = /* @__PURE__ */ _decode(ZodRealError);
4364
4394
  var encodeAsync = /* @__PURE__ */ _encodeAsync(ZodRealError);
4365
4395
  var decodeAsync = /* @__PURE__ */ _decodeAsync(ZodRealError);
4366
4396
  var safeEncode = /* @__PURE__ */ _safeEncode(ZodRealError);
@@ -4421,7 +4451,7 @@ var ZodType = /*@__PURE__*/ $constructor("ZodType", (inst, def) => {
4421
4451
  inst.safeParseAsync = async (data, params) => safeParseAsync(inst, data, params);
4422
4452
  inst.spa = inst.safeParseAsync;
4423
4453
  inst.encode = (data, params) => encode(inst, data, params);
4424
- inst.decode = (data, params) => decode(inst, data, params);
4454
+ inst.decode = (data, params) => decode$1(inst, data, params);
4425
4455
  inst.encodeAsync = async (data, params) => encodeAsync(inst, data, params);
4426
4456
  inst.decodeAsync = async (data, params) => decodeAsync(inst, data, params);
4427
4457
  inst.safeEncode = (data, params) => safeEncode(inst, data, params);
@@ -5228,6 +5258,10 @@ function pipe(in_, out) {
5228
5258
  out
5229
5259
  });
5230
5260
  }
5261
+ var ZodPreprocess = /*@__PURE__*/ $constructor("ZodPreprocess", (inst, def) => {
5262
+ ZodPipe.init(inst, def);
5263
+ $ZodPreprocess.init(inst, def);
5264
+ });
5231
5265
  var ZodReadonly = /*@__PURE__*/ $constructor("ZodReadonly", (inst, def) => {
5232
5266
  $ZodReadonly.init(inst, def);
5233
5267
  ZodType.init(inst, def);
@@ -5286,6 +5320,13 @@ function _instanceof(cls, params = {}) {
5286
5320
  };
5287
5321
  return inst;
5288
5322
  }
5323
+ function preprocess(fn, schema) {
5324
+ return new ZodPreprocess({
5325
+ type: "pipe",
5326
+ in: transform(fn),
5327
+ out: schema
5328
+ });
5329
+ }
5289
5330
  //#endregion
5290
5331
  //#region ../../node_modules/zod/v4/classic/compat.js
5291
5332
  /** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
@@ -5402,6 +5443,12 @@ Object.fromEntries([
5402
5443
  icon: "shapes",
5403
5444
  order: 38
5404
5445
  },
5446
+ {
5447
+ id: "scenes",
5448
+ label: "Scenes",
5449
+ icon: "scan-eye",
5450
+ order: 36
5451
+ },
5405
5452
  {
5406
5453
  id: "analytics",
5407
5454
  label: "Analytics",
@@ -13071,11 +13118,33 @@ var NotificationFormatSchema = _enum([
13071
13118
  * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
13072
13119
  * renderer's icon set; "acknowledge" survives an adapter that draws it
13073
13120
  * differently.
13121
+ *
13122
+ * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
13123
+ *
13124
+ * These names are for US. **No adapter may forward one verbatim.** Each maps
13125
+ * the whole set onto its own renderer's vocabulary through a
13126
+ * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
13127
+ * fallback, so adding a member here fails every adapter's build until someone
13128
+ * decides its glyph, which is the only place that decision can be made
13129
+ * honestly.
13130
+ *
13131
+ * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
13132
+ * `disarm` straight through; iOS feeds that string to
13133
+ * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
13134
+ * and every snooze and alarm button arrived BLANK. A pass-through is not a
13135
+ * mapping, and "the field is documented" is not "the value renders".
13136
+ *
13137
+ * Adding a member is TRAIN-BOUND. The enum lives in the published
13138
+ * `@camstack/server` closure and the cap seam validates against the HUB's copy,
13139
+ * so an addon that emits a token the running hub does not know does not lose an
13140
+ * icon — its whole `send` fails Zod validation and the notification never
13141
+ * arrives. Never emit a new token from an addon before the train carrying it.
13074
13142
  */
13075
13143
  var NotificationActionIconSchema = _enum([
13076
13144
  "acknowledge",
13077
13145
  "dismiss",
13078
13146
  "silence",
13147
+ "snooze",
13079
13148
  "view",
13080
13149
  "play",
13081
13150
  "open",
@@ -13083,9 +13152,13 @@ var NotificationActionIconSchema = _enum([
13083
13152
  "lock",
13084
13153
  "unlock",
13085
13154
  "arm",
13155
+ "arm-home",
13156
+ "arm-away",
13157
+ "arm-night",
13086
13158
  "disarm",
13087
13159
  "light",
13088
- "alert"
13160
+ "alert",
13161
+ "camera"
13089
13162
  ]);
13090
13163
  /** A single tap-through action button. */
13091
13164
  var NotificationActionSchema = object({
@@ -13285,6 +13358,24 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
13285
13358
  targetId: string(),
13286
13359
  enabled: boolean()
13287
13360
  }), _void(), { kind: "mutation" });
13361
+ new Set([
13362
+ {
13363
+ id: "person",
13364
+ name: "Person"
13365
+ },
13366
+ {
13367
+ id: "vehicle",
13368
+ name: "Vehicle"
13369
+ },
13370
+ {
13371
+ id: "animal",
13372
+ name: "Animal"
13373
+ },
13374
+ {
13375
+ id: "package",
13376
+ name: "Package"
13377
+ }
13378
+ ].map((l) => l.id));
13288
13379
  var COCO_TO_MACRO = {
13289
13380
  mapping: {
13290
13381
  person: "person",
@@ -14077,11 +14168,15 @@ var NcSystemEventKindSchema = _enum([
14077
14168
  "stream-offline",
14078
14169
  "node-online",
14079
14170
  "node-offline",
14171
+ "node-inference-unavailable",
14172
+ "detection-blind",
14080
14173
  "addon-update-available",
14081
14174
  "server-update-available",
14082
14175
  "alarm-triggered",
14083
14176
  "alarm-armed",
14084
14177
  "alarm-disarmed",
14178
+ "alarm-arming",
14179
+ "alarm-arm-refused",
14085
14180
  "camera-online",
14086
14181
  "camera-offline",
14087
14182
  "camera-disabled",
@@ -14136,7 +14231,16 @@ var NcScheduleSchema = object({
14136
14231
  });
14137
14232
  /** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
14138
14233
  var NcPlateMatcherSchema = object({
14139
- values: array(string().min(1)).min(1),
14234
+ /**
14235
+ * Plate texts (or gallery vehicle names) to match. EMPTY = **any plate the
14236
+ * pipeline could read** — the plate half of "no selection = no narrowing",
14237
+ * and the switch that says this rule is about vehicles that were IDENTIFIED
14238
+ * rather than merely seen. A subject carrying no plate still fails.
14239
+ *
14240
+ * The `.min(1)` this used to carry made that state unauthorable; nothing has
14241
+ * ever persisted an empty list, so widening it cannot change an existing rule.
14242
+ */
14243
+ values: array(string().min(1)),
14140
14244
  /** Max Levenshtein distance after normalization (uppercase alphanumeric). */
14141
14245
  maxDistance: number().int().min(0).max(3).default(1)
14142
14246
  });
@@ -14375,18 +14479,47 @@ var NcConditionsSchema = object({
14375
14479
  */
14376
14480
  labelEquals: array(string().min(1)).optional(),
14377
14481
  /**
14378
- * Identity matcher. P1 boundary: matched against the record's collapsed
14379
- * `label` (the identity display name propagated by the face pipeline) —
14380
- * identity-ID matching rides in P2 when identity ids reach the record.
14482
+ * KNOWN FACES the rule's identity scope, and the switch that says the rule
14483
+ * is about recognised people at all.
14484
+ *
14485
+ * Three states, and the empty one is the point:
14486
+ *
14487
+ * | value | meaning |
14488
+ * | --- | --- |
14489
+ * | absent | the rule does not care who it is; an unrecognised person matches |
14490
+ * | `[]` | **only known faces** — any identity in the gallery, nobody in particular |
14491
+ * | a list | only these identities |
14492
+ *
14493
+ * `[]` is the repo-wide "no selection = no narrowing" reading (an absent
14494
+ * `devices` list is every device), applied one level down: the operator has
14495
+ * turned the face scope ON and narrowed it to nothing, which is every known
14496
+ * face. No second field states the same thing — a switch that can disagree
14497
+ * with the list under it is worse than no switch (D62).
14498
+ *
14499
+ * MEMBERS ARE FACE-GALLERY `Identity.id`s (uuid), not display names. A name is
14500
+ * renameable, and a rule authored on "Gianluca" went silently dark the moment
14501
+ * the operator fixed the spelling. The id reaches the record on
14502
+ * `LabelAttribution.identityId`; the name is what the editor shows and what
14503
+ * `{{label}}` renders.
14504
+ *
14505
+ * Rules written before this carry NAMES, and are resolved to ids lazily at
14506
+ * load (`NcRuleStore.load`) against the live gallery — a name nothing answers
14507
+ * for is left as it stands and reported, never dropped. The engine also
14508
+ * accepts a display-name hit as a compatibility leg, so a rule whose
14509
+ * migration could not resolve keeps matching exactly what it matched before.
14381
14510
  */
14382
14511
  identities: array(string().min(1)).optional(),
14383
- /** Fuzzy plate matcher against the record's `label` (plate text). */
14512
+ /**
14513
+ * KNOWN PLATES / VEHICLES — the plate mirror of {@link identities}, including
14514
+ * the empty-list reading: `values: []` is "any plate the OCR could read",
14515
+ * a non-empty list is those plates (fuzzily). See {@link NcPlateMatcherSchema}.
14516
+ */
14384
14517
  plates: NcPlateMatcherSchema.optional(),
14385
14518
  /**
14386
- * Identity EXCLUDE — mirror of {@link identities} with `notIn` semantics.
14387
- * Same P1 boundary: matched against the record's collapsed `label` (the
14388
- * identity display name). A record with NO label passes (nothing to
14389
- * exclude), unlike the include variant which fails on an absent label.
14519
+ * Identity EXCLUDE — mirror of {@link identities} with `notIn` semantics, and
14520
+ * the same id members and the same lazy name→id migration. A record with NO
14521
+ * identity passes (nothing to exclude), unlike the include variant which
14522
+ * fails on an unrecognised subject. An EMPTY list excludes nobody.
14390
14523
  */
14391
14524
  identitiesExclude: array(string().min(1)).optional(),
14392
14525
  /**
@@ -14778,7 +14911,80 @@ var NcRuleInputSchema = object({
14778
14911
  * a rule that predates the gate must keep delivering byte-for-byte as it
14779
14912
  * did, and absent is the only way to say that without a migration.
14780
14913
  */
14781
- confirm: NcConfirmSchema.optional()
14914
+ confirm: NcConfirmSchema.optional(),
14915
+ /**
14916
+ * WAIT for face/plate recognition before saying anything.
14917
+ *
14918
+ * A notification's TEXT is frozen at enqueue and its media is re-resolved at
14919
+ * send; the identity is neither. A face is confirmed after `confirmFrames`
14920
+ * agreeing observations — p50 **11.4 s** after the track was first seen,
14921
+ * measured on this hub — and an `immediate` rule enqueues on the first object
14922
+ * event, seconds before that. So "Gianluca è arrivato" is unsayable on the
14923
+ * immediate path, and no amount of media re-resolution fixes a sentence.
14924
+ *
14925
+ * Only two honest answers exist, and this flag picks between them. It has
14926
+ * effect ONLY on a rule that declares a recognition scope
14927
+ * ({@link NcConditions.identities} or {@link NcConditions.plates}) — on any
14928
+ * other rule there is nothing to wait for and the flag is inert.
14929
+ *
14930
+ * | value | what happens |
14931
+ * | --- | --- |
14932
+ * | `true` | the rule stops firing on the object event and fires at TRACK CLOSE instead, once, with the name — later, and complete |
14933
+ * | absent / `false` | it fires at once WITHOUT the name, and if recognition lands before the track closes a SECOND, "…is Gianluca" notification follows (one per track, per rule, per target) |
14934
+ *
14935
+ * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
14936
+ * on the addon cap path, and absent has to keep meaning exactly what every
14937
+ * rule authored before this field meant.
14938
+ *
14939
+ * The cost of `true` is stated here because the editor states it too: a rule
14940
+ * that waits also inherits track-close SEMANTICS — its `zones` condition
14941
+ * tests every zone the track visited and a `crossing` condition can no longer
14942
+ * be satisfied, because a closed track carries no crossing.
14943
+ */
14944
+ waitForEnhancement: boolean().optional(),
14945
+ /**
14946
+ * GROUP a burst of subjects into ONE notification that grows.
14947
+ *
14948
+ * Seconds of quiet after the last matching subject before the burst is
14949
+ * considered over. While it is open, the first subject enqueues immediately —
14950
+ * **exactly as today, with no added latency** — and every real growth (a new
14951
+ * subject, or a name confirmed on one already in it) REPLACES that
14952
+ * notification with an updated one naming everybody. The push carries the
14953
+ * group's own coalescing tag, so the phone replaces rather than stacks.
14954
+ *
14955
+ * `0` / absent = off, and off is today's behaviour byte for byte.
14956
+ *
14957
+ * ### Why an idle cutoff and not a window
14958
+ *
14959
+ * The measured seven-person arrival on device 590 spans 110 s with every
14960
+ * internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
14961
+ * idle cutoff holds it as one and ends it when the arrival actually ends.
14962
+ * 30 is Frigate's shipped value for the same decision.
14963
+ *
14964
+ * ### What it replaces
14965
+ *
14966
+ * The blind cooldown, which collapses a burst by DISCARDING it. Measured on
14967
+ * device 615 / *Persona su Uscio* over six days: 116 qualifying tracks → 74
14968
+ * notifications, **44 (37.9%) suppressed outright**, 23 of them overlapping a
14969
+ * track that did fire and 7 carrying a confirmed identity nobody heard about.
14970
+ * A group collapses the same volume by MERGING, so the cooldown becomes a
14971
+ * budget over GROUPS — which is what it always meant — and a growth is never
14972
+ * throttled by the window its own first member spent.
14973
+ *
14974
+ * ### Interaction with {@link waitForEnhancement}
14975
+ *
14976
+ * They compose, and the order matters. `waitForEnhancement` defers the rule to
14977
+ * TRACK CLOSE, so with both set the group is opened by the first member to
14978
+ * CLOSE — already carrying its name — and grows as later members close. That
14979
+ * is later, and complete. With grouping alone the group opens on the first
14980
+ * object event and picks up names as they are confirmed, through the growth
14981
+ * path. Neither combination fires twice for one subject.
14982
+ *
14983
+ * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
14984
+ * on the addon cap path, so absent must keep meaning what it meant before this
14985
+ * field existed.
14986
+ */
14987
+ groupIdleSec: number().int().min(0).max(600).optional()
14782
14988
  });
14783
14989
  /**
14784
14990
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -15290,7 +15496,87 @@ var MethodAccessSchema = _enum([
15290
15496
  var AllowedProviderSchema = union([literal("*"), array(string())]);
15291
15497
  var AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
15292
15498
  var CapScopeSchema = _enum(["device", "system"]);
15293
- var TokenScopeSchema = discriminatedUnion("type", [
15499
+ /**
15500
+ * DeviceSelector (scope model v3 — 2026-08-12).
15501
+ *
15502
+ * A `device` grant no longer carries a frozen list of deviceIds. It carries
15503
+ * a SELECTOR the matcher resolves against the live fleet, so the grant can be
15504
+ * DYNAMIC: a `types:['camera']` selector automatically covers a camera added
15505
+ * AFTER the grant was minted — no re-grant, no re-login.
15506
+ *
15507
+ * - `all` — every device in the deployment. The broad viewer/operator
15508
+ * lever without a `category` grant (a `category` grant also covers device
15509
+ * caps that carry no deviceId; `all` is specifically the device set).
15510
+ * - `ids` — an explicit deviceId list. This is what a v2 `device:[…]`
15511
+ * grant migrates to (see {@link TokenScopeSchema}); STATIC — a new camera
15512
+ * is NOT covered until the grant is edited.
15513
+ * - `types` — every device of a `DeviceType` (e.g. every `camera`).
15514
+ * DYNAMIC. A device that changes type, or a new device of the type,
15515
+ * re-resolves on the next request.
15516
+ * - `locations` — every device whose operator-assigned `location` label is
15517
+ * in the set (e.g. "Garden", "Front door"). DYNAMIC. A device with a
15518
+ * null/unset location matches NO `locations` selector.
15519
+ */
15520
+ var DeviceSelectorSchema = discriminatedUnion("kind", [
15521
+ object({ kind: literal("all") }),
15522
+ object({
15523
+ kind: literal("ids"),
15524
+ ids: array(number().int()).min(1)
15525
+ }),
15526
+ object({
15527
+ kind: literal("types"),
15528
+ types: array(_enum(DeviceType)).min(1)
15529
+ }),
15530
+ object({
15531
+ kind: literal("locations"),
15532
+ locations: array(string().min(1)).min(1)
15533
+ })
15534
+ ]);
15535
+ var DeviceTokenScopeSchema = object({
15536
+ type: literal("device"),
15537
+ /** The device SET this grant covers — resolved against the live fleet. */
15538
+ selector: DeviceSelectorSchema,
15539
+ access: array(MethodAccessSchema).min(1),
15540
+ /**
15541
+ * Whether a grant on a PARENT device transparently covers its accessory
15542
+ * CHILDREN (siren / floodlight / PIR) via the persisted-parentage walk.
15543
+ * Direction is parent → children ONLY.
15544
+ *
15545
+ * Absent → the matcher DERIVES it from the access flavour: `view`
15546
+ * inherits (a camera viewer sees the camera's accessories), `create` /
15547
+ * `delete` do NOT (actuating/removing a child is an explicit act the
15548
+ * operator must grant on the child, not inherit from the parent). Set it
15549
+ * explicitly to override that default per grant.
15550
+ */
15551
+ includeLinked: boolean().optional()
15552
+ });
15553
+ /**
15554
+ * v2 → v3 lazy migration. A pre-v3 `device` grant carried
15555
+ * `targets: string[]` (stringified deviceIds); it rewrites to the equivalent
15556
+ * `selector: {kind:'ids', ids}`. Applied as a `preprocess` so it runs on
15557
+ * EVERY parse path — stored records AND the JWT-carried scope arrays
15558
+ * normalised at the request boundary ({@link normalizeTokenScopes} in
15559
+ * `device-selector.ts`). Chosen over a one-time DB migration because a
15560
+ * migration cannot reach a JWT already in a client's hands; parse-time
15561
+ * migration covers both without a flag day. No cast — the raw object is read
15562
+ * through `Reflect.get` (its static type is `unknown`).
15563
+ */
15564
+ function migrateLegacyTokenScope(raw) {
15565
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
15566
+ if (Reflect.get(raw, "type") !== "device") return raw;
15567
+ if (Reflect.get(raw, "selector") !== void 0) return raw;
15568
+ const targets = Reflect.get(raw, "targets");
15569
+ if (!Array.isArray(targets)) return raw;
15570
+ return {
15571
+ type: "device",
15572
+ selector: {
15573
+ kind: "ids",
15574
+ ids: targets.map((t) => typeof t === "string" ? Number(t) : t).filter((n) => typeof n === "number" && Number.isInteger(n))
15575
+ },
15576
+ access: Reflect.get(raw, "access")
15577
+ };
15578
+ }
15579
+ var TokenScopeSchema = preprocess(migrateLegacyTokenScope, discriminatedUnion("type", [
15294
15580
  object({
15295
15581
  type: literal("category"),
15296
15582
  target: CapScopeSchema,
@@ -15306,18 +15592,8 @@ var TokenScopeSchema = discriminatedUnion("type", [
15306
15592
  target: string(),
15307
15593
  access: array(MethodAccessSchema).min(1)
15308
15594
  }),
15309
- object({
15310
- type: literal("device"),
15311
- /**
15312
- * One or more deviceIds (serialised as strings for wire-format
15313
- * consistency with the rest of the union). Matcher accepts if
15314
- * `input.deviceId` ∈ `targets`. Array shape avoids the row-explosion
15315
- * of one scope-per-device when granting access to a set of cameras.
15316
- */
15317
- targets: array(string()).min(1),
15318
- access: array(MethodAccessSchema).min(1)
15319
- })
15320
- ]);
15595
+ DeviceTokenScopeSchema
15596
+ ]));
15321
15597
  object({
15322
15598
  id: string(),
15323
15599
  username: string(),
@@ -15634,7 +15910,7 @@ var TrackEnvelopeSchema = object({
15634
15910
  * `snapshots[]` references — megabytes across a page of tracks. `slim`
15635
15911
  * keeps every scalar the list surfaces actually render (ids, class(es),
15636
15912
  * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
15637
- * zonesVisited, bestEventId, envelope, hasFace) and returns `positions` /
15913
+ * zonesVisited, bestEventId, envelope, hasFace, hasRider) and returns `positions` /
15638
15914
  * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
15639
15915
  * `getTrack`. Mirrors the event-store `projection` convention
15640
15916
  * (`getObjectEvents` et al.).
@@ -15770,7 +16046,21 @@ union([literal(1), literal(2)]);
15770
16046
  var LabelAttributionSchema = object({
15771
16047
  stepId: string(),
15772
16048
  modelId: string().optional(),
15773
- decidedAt: number()
16049
+ decidedAt: number(),
16050
+ /**
16051
+ * The GALLERY id behind a recognised tier-2 label — a face-gallery
16052
+ * `Identity.id` or a plate-gallery `Vehicle.id` (both `randomUUID`).
16053
+ *
16054
+ * The text alone is a DISPLAY NAME, and a display name is renameable: a
16055
+ * notification rule authored on "Gianluca" stopped matching the moment the
16056
+ * operator fixed the spelling in the gallery, and nothing said so. The id is
16057
+ * the thing that does not move, so it is what a rule matches on
16058
+ * (`NcConditions.identities`) and the text is what a human is shown.
16059
+ *
16060
+ * Absent when the label names no gallery row — a plate the OCR read but no
16061
+ * vehicle claims, a sub-class, a species, any tier-1 value.
16062
+ */
16063
+ identityId: string().optional()
15774
16064
  });
15775
16065
  /**
15776
16066
  * The TIERED label model (roadmap 4g), spread into `TrackSchema` and
@@ -15907,6 +16197,28 @@ var TrackSchema = object({
15907
16197
  * `=== true` and render nothing otherwise, never infer "no face".
15908
16198
  */
15909
16199
  hasFace: boolean().optional(),
16200
+ /**
16201
+ * This subject CONTAINS a folded rider — a person the rider-pairing step
16202
+ * ([D34](../decisions/adr-0034.md)) removed from the frame BEFORE the tracker,
16203
+ * so the passage is tracked once and as a VEHICLE.
16204
+ *
16205
+ * It exists because the fold's record was dishonest. D34 and the code both
16206
+ * said "the person is not lost — it is reported so both entities stay on the
16207
+ * record"; in fact the pair went into a per-processor RAM field behind an
16208
+ * accessor nobody called, and every durable surface said `vehicle`, full
16209
+ * stop. This is the composition note that makes the row true.
16210
+ *
16211
+ * A COMPOSITION, never a class and never a label. "This vehicle contains a
16212
+ * person" is not an answer to "what is this" — both label tiers would refuse
16213
+ * a macro token anyway (D89), and correctly. Nothing here changes what the
16214
+ * subject IS: a cyclist stays one vehicle track, occupancy still counts one,
16215
+ * and a `person` rule still does not fire for someone cycling past.
16216
+ *
16217
+ * **Absent ≠ false**, exactly like {@link hasFace}: every row written before
16218
+ * the column, and every hub that predates the field, omits it. Test
16219
+ * `=== true` and render nothing otherwise — never infer "no rider".
16220
+ */
16221
+ hasRider: boolean().optional(),
15910
16222
  ...TrackFlagFields,
15911
16223
  ...TrackRetrainFields
15912
16224
  });
@@ -17314,10 +17626,26 @@ var detectionFpsField = {
17314
17626
  default: 10,
17315
17627
  step: 1
17316
17628
  };
17629
+ /**
17630
+ * The occupancy re-check interval. DEFAULT 300 s (2026-08-13 — was 30 s).
17631
+ *
17632
+ * The recheck is now on by default (a parked car is invisible to occupancy
17633
+ * rules until the stationary registry has been rebuilt by motion, which after a
17634
+ * restart may be never on a quiet camera). Each cycle re-subscribes a detection
17635
+ * session — an RTSP re-dial — so the switch is only affordable at a WIDE
17636
+ * interval: 300 s is ~12 re-dials an hour per camera, against 120 at the old
17637
+ * 30 s. A parked car is therefore counted within 5 minutes of a restart.
17638
+ *
17639
+ * Why not wider: `max` is 300 and raising it is TRAIN-BOUND, not addon-bound —
17640
+ * the host validates `attachCamera` against ITS copy of this schema, so a
17641
+ * runner asked for 600 would be rejected by the hub until a `@camstack/server`
17642
+ * carrying the wider bound is installed everywhere. 300 is the widest value
17643
+ * that ships with an addon deploy.
17644
+ */
17317
17645
  var occupancyRecheckSecField = {
17318
17646
  min: 0,
17319
17647
  max: 300,
17320
- default: 30,
17648
+ default: 300,
17321
17649
  step: 5
17322
17650
  };
17323
17651
  var occupancyRecheckFramesField = {
@@ -17515,15 +17843,21 @@ var RunnerCameraConfigSchema = object({
17515
17843
  */
17516
17844
  onboardMotionDrivesAnalyzer: boolean().default(true),
17517
17845
  /**
17518
- * Master toggle for the occupancy re-check. When `false` (DEFAULT) the runner
17519
- * never arms the periodic recheck timer, regardless of `occupancyRecheckSec`
17520
- * this is off by default because the recheck re-subscribes a detection session
17521
- * every N seconds while `watching`, a major source of pull-decoder re-dial
17522
- * churn (each cycle creates+tears a session → RTSP re-dial → latency). The
17846
+ * Master toggle for the occupancy re-check. When `false` the runner never arms
17847
+ * the periodic recheck timer, regardless of `occupancyRecheckSec`; the
17523
17848
  * `occupancyRecheckSec` / `occupancyRecheckFrames` sliders only take effect
17524
17849
  * (and only render) when this is enabled.
17525
- */
17526
- occupancyRecheckEnabled: boolean().default(false),
17850
+ *
17851
+ * DEFAULT `true` since 2026-08-13 (was `false`). It was off because the
17852
+ * recheck re-subscribes a detection session every N seconds while `watching`
17853
+ * — each cycle creates+tears a session ⇒ an RTSP re-dial ⇒ latency, a major
17854
+ * pull-decoder churn source. What that bought was a blind spot: a STATIONARY
17855
+ * object is counted only while the stationary registry holds it, and the
17856
+ * registry rebuilds from motion, so after a restart a parked car was invisible
17857
+ * to every occupancy rule until something moved in front of it. The churn is
17858
+ * now paid on the interval instead — see `occupancyRecheckSecField`.
17859
+ */
17860
+ occupancyRecheckEnabled: boolean().default(true),
17527
17861
  occupancyRecheckSec: number().min(occupancyRecheckSecField.min).max(occupancyRecheckSecField.max).default(occupancyRecheckSecField.default),
17528
17862
  occupancyRecheckFrames: number().min(occupancyRecheckFramesField.min).max(occupancyRecheckFramesField.max).default(occupancyRecheckFramesField.default),
17529
17863
  /**
@@ -23626,7 +23960,7 @@ method(object({
23626
23960
  toMs: number()
23627
23961
  }), RecordingAvailabilitySchema, {
23628
23962
  kind: "query",
23629
- auth: "admin"
23963
+ auth: "protected"
23630
23964
  }), method(object({
23631
23965
  deviceId: number(),
23632
23966
  fromMs: number(),
@@ -23634,14 +23968,14 @@ method(object({
23634
23968
  tzOffsetMinutes: number()
23635
23969
  }), RecordingDaysSchema, {
23636
23970
  kind: "query",
23637
- auth: "admin"
23971
+ auth: "protected"
23638
23972
  }), method(object({
23639
23973
  deviceId: number(),
23640
23974
  fromMs: number(),
23641
23975
  toMs: number()
23642
23976
  }), RecordingManifestSchema, {
23643
23977
  kind: "query",
23644
- auth: "admin"
23978
+ auth: "protected"
23645
23979
  }), method(object({}), RecordingStorageUsageSchema, {
23646
23980
  kind: "query",
23647
23981
  auth: "admin"
@@ -23936,9 +24270,33 @@ method(object({
23936
24270
  * settings-contribution methods. `status.kind:'push'` — the engine pushes on
23937
24271
  * every hysteresis flip / availability change; consumers never poll.
23938
24272
  */
23939
- /** Extensible condition tag. Seeded 'day' | 'night'; open by design so more can
23940
- * be added without a wire break (matching falls back to any-condition refs). */
24273
+ /** Extensible condition tag. Seeded 'day' | 'ir' (the two variants the operator
24274
+ * captures) plus 'night' | 'dawn' | 'dusk' from the resolver's sun-times band.
24275
+ * Open by design so more can be added without a wire break.
24276
+ *
24277
+ * Matching does NOT fall back across conditions: cross-condition cosines are
24278
+ * not comparable, so "I have never seen this scene in this light" is reported
24279
+ * as `unknown`, never guessed. A day reference scored against an IR frame
24280
+ * collapses the cosine and would latch a false alarm every single night. */
23941
24281
  var SceneConditionSchema = string();
24282
+ /** `matched` = the baseline is what we see; `diverged` = it demonstrably is not;
24283
+ * `unknown` = we cannot judge (no reference for this condition, encoder model
24284
+ * changed, view shifted, no snapshot). `unknown` is a real value, not a null,
24285
+ * and never counts toward hysteresis in either direction. */
24286
+ var SceneVerdictSchema = _enum([
24287
+ "matched",
24288
+ "diverged",
24289
+ "unknown"
24290
+ ]);
24291
+ /** Why a scene cannot judge. Named, because this feature's failure mode is
24292
+ * silence that reads as "nothing has happened". */
24293
+ var SceneUnavailableSchema = _enum([
24294
+ "no-reference-for-condition",
24295
+ "view-shifted",
24296
+ "no-vision-profile",
24297
+ "encoder-model-changed",
24298
+ "no-snapshot"
24299
+ ]);
23942
24300
  /** One captured reference — condition-tagged, model-version-gated. `embedding`
23943
24301
  * is `number[]` (Float32Array does NOT survive MsgPack/UDS). */
23944
24302
  var SceneReferenceSchema = object({
@@ -23946,7 +24304,14 @@ var SceneReferenceSchema = object({
23946
24304
  modelId: string(),
23947
24305
  condition: SceneConditionSchema,
23948
24306
  capturedAt: number(),
23949
- thumbnailMediaId: string().optional()
24307
+ thumbnailMediaId: string().optional(),
24308
+ /** Whole-frame (downscaled) embedding captured alongside the ROI crop. The
24309
+ * anti-view-shift anchor: a bumped camera, a PTZ preset or a re-aim makes the
24310
+ * normalized rect frame a different piece of world, and the scene would
24311
+ * diverge forever with a perfectly plausible cosine. Checked LAZILY, only
24312
+ * when hysteresis is about to flip — one extra encode per candidate
24313
+ * transition, not per poll. */
24314
+ anchorEmbedding: array(number()).optional()
23950
24315
  });
23951
24316
  var SceneMonitorStateSchema = object({
23952
24317
  id: string(),
@@ -23968,6 +24333,25 @@ var SceneCheckSchema = discriminatedUnion("mode", [object({
23968
24333
  profileId: string().optional(),
23969
24334
  hysteresisCount: number().int().positive()
23970
24335
  })]);
24336
+ var SCENE_DEFAULT_ANCHOR_THRESHOLD = .85;
24337
+ /**
24338
+ * Vision-model adjudication of a candidate flip. Field names deliberately
24339
+ * mirror `NcConfirmSchema` so an operator meets one vocabulary, not two.
24340
+ *
24341
+ * `onTimeout` defaults to **'hold'**, the OPPOSITE of `NcConfirmGate`'s
24342
+ * fail-open: a notification suppressed is the worse error there, but a vision
24343
+ * model that timed out has not told us the bin is gone, and a latch is a
24344
+ * stateful claim that costs the operator a trip to reset.
24345
+ */
24346
+ var SceneConfirmSchema = object({
24347
+ enabled: boolean().default(false),
24348
+ prompt: string().min(1).max(1e3),
24349
+ profileId: string().optional(),
24350
+ timeoutMs: number().int().min(1e3).max(2e4).default(8e3),
24351
+ maxImagePx: number().int().min(64).max(2048).default(448),
24352
+ /** What a timeout / unavailable model means for the PENDING flip. */
24353
+ onTimeout: _enum(["flip", "hold"]).default("hold")
24354
+ });
23971
24355
  var SceneMonitorSchema = object({
23972
24356
  id: string(),
23973
24357
  label: string(),
@@ -23986,7 +24370,41 @@ var SceneMonitorSchema = object({
23986
24370
  lastConfidence: number().nullable(),
23987
24371
  currentCondition: SceneConditionSchema.nullable(),
23988
24372
  availability: _enum(["ok", "unavailable"]),
23989
- unavailableReason: string().nullable()
24373
+ unavailableReason: string().nullable(),
24374
+ /** Which state is "the initial screen". `null` until the first capture. */
24375
+ baselineStateId: string().nullable(),
24376
+ /** Which boolean drives notification rules and any export. */
24377
+ emit: _enum(["latched", "live"]).default("latched"),
24378
+ /** Live: does the region match the baseline RIGHT NOW. */
24379
+ verdict: SceneVerdictSchema,
24380
+ /** Has it been `diverged` at least once since `armedAt` — the operator's boolean. */
24381
+ latched: boolean(),
24382
+ /** Last reset (or creation). */
24383
+ armedAt: number(),
24384
+ divergedAt: number().nullable(),
24385
+ restoredAt: number().nullable(),
24386
+ /** A check is only COUNTED when the device has been quiet this long. Motion
24387
+ * during the window DISCARDS the observation — a car pulling up in front of
24388
+ * the bin must not be able to spend hysteresis credit. */
24389
+ quietSeconds: number().int().min(0).max(3600).default(60),
24390
+ /** An observation only advances the pending count when it is at least this
24391
+ * far from the previously counted one, so N agreeing checks span real time
24392
+ * rather than N adjacent polls inside one occlusion. */
24393
+ minObservationSpacingSec: number().int().min(0).max(3600).default(120),
24394
+ /** Vision-model adjudication of a candidate flip. Similarity primary only. */
24395
+ confirm: SceneConfirmSchema.optional(),
24396
+ /** Whole-frame anchor cosine below which a flip is REFUSED as `view-shifted`. */
24397
+ anchorThreshold: number().min(0).max(1).default(SCENE_DEFAULT_ANCHOR_THRESHOLD),
24398
+ /** Clear the latch on its own when the scene matches again? Default false —
24399
+ * `restoredAt` and the `scene-restored` edge are recorded regardless, so an
24400
+ * automation can react to the bin coming back without the operator's own
24401
+ * alarm silently clearing itself. */
24402
+ autoRestore: boolean().default(false),
24403
+ /** Named cause when `verdict === 'unknown'`. */
24404
+ unavailable: SceneUnavailableSchema.nullable(),
24405
+ /** Conditions that have at least one comparable reference — the coverage line
24406
+ * ("day ✓ · ir ✓ · dusk ✗") that turns a silent fallback into a visible fact. */
24407
+ coveredConditions: array(SceneConditionSchema)
23990
24408
  });
23991
24409
  var SceneMonitorStatusSchema = object({
23992
24410
  monitors: array(SceneMonitorSchema),
@@ -24019,7 +24437,14 @@ DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSche
24019
24437
  "both"
24020
24438
  ]).optional(),
24021
24439
  checkIntervalSec: number().optional(),
24022
- check: SceneCheckSchema.optional()
24440
+ check: SceneCheckSchema.optional(),
24441
+ emit: _enum(["latched", "live"]).optional(),
24442
+ quietSeconds: number().int().min(0).max(3600).optional(),
24443
+ minObservationSpacingSec: number().int().min(0).max(3600).optional(),
24444
+ anchorThreshold: number().min(0).max(1).optional(),
24445
+ autoRestore: boolean().optional(),
24446
+ /** `null` clears the vision-model adjudicator. */
24447
+ confirm: SceneConfirmSchema.nullable().optional()
24023
24448
  })
24024
24449
  }), _void(), {
24025
24450
  kind: "mutation",
@@ -24056,6 +24481,14 @@ DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSche
24056
24481
  }), _void(), {
24057
24482
  kind: "mutation",
24058
24483
  auth: "admin"
24484
+ }), method(object({
24485
+ deviceId: number(),
24486
+ monitorId: string(),
24487
+ /** Defaults to TRUE at the provider seam — see `SCENE_RESET_RECAPTURES`. */
24488
+ recapture: boolean().optional()
24489
+ }), _void(), {
24490
+ kind: "mutation",
24491
+ auth: "admin"
24059
24492
  });
24060
24493
  /**
24061
24494
  * Per-stage gating mode applied to the zones a rule references.
@@ -29349,6 +29782,12 @@ Object.freeze({
29349
29782
  addonId: null,
29350
29783
  access: "create"
29351
29784
  },
29785
+ "sceneMonitor.resetScene": {
29786
+ capName: "scene-monitor",
29787
+ capScope: "device",
29788
+ addonId: null,
29789
+ access: "delete"
29790
+ },
29352
29791
  "sceneMonitor.updateScene": {
29353
29792
  capName: "scene-monitor",
29354
29793
  capScope: "device",
@@ -30640,6 +31079,1683 @@ Object.freeze({
30640
31079
  access: "create"
30641
31080
  }
30642
31081
  });
31082
+ Object.freeze({
31083
+ "accessories.setChildHidden": [{
31084
+ name: "childDeviceId",
31085
+ form: "single",
31086
+ optional: false
31087
+ }, {
31088
+ name: "deviceId",
31089
+ form: "single",
31090
+ optional: false
31091
+ }],
31092
+ "addonSettings.getDeviceSettings": [{
31093
+ name: "deviceId",
31094
+ form: "single",
31095
+ optional: false
31096
+ }],
31097
+ "addonSettings.updateDeviceSettings": [{
31098
+ name: "deviceId",
31099
+ form: "single",
31100
+ optional: false
31101
+ }],
31102
+ "alarmPanel.arm": [{
31103
+ name: "deviceId",
31104
+ form: "single",
31105
+ optional: false
31106
+ }],
31107
+ "alarmPanel.disarm": [{
31108
+ name: "deviceId",
31109
+ form: "single",
31110
+ optional: false
31111
+ }],
31112
+ "alarmPanel.trigger": [{
31113
+ name: "deviceId",
31114
+ form: "single",
31115
+ optional: false
31116
+ }],
31117
+ "audioAnalysis.resolveDeviceSettings": [{
31118
+ name: "deviceId",
31119
+ form: "single",
31120
+ optional: false
31121
+ }],
31122
+ "audioAnalyzer.classify": [{
31123
+ name: "deviceId",
31124
+ form: "single",
31125
+ optional: true
31126
+ }],
31127
+ "audioMetrics.getCurrentSnapshot": [{
31128
+ name: "deviceId",
31129
+ form: "single",
31130
+ optional: false
31131
+ }],
31132
+ "audioMetrics.getHistory": [{
31133
+ name: "deviceId",
31134
+ form: "single",
31135
+ optional: false
31136
+ }],
31137
+ "automationControl.disable": [{
31138
+ name: "deviceId",
31139
+ form: "single",
31140
+ optional: false
31141
+ }],
31142
+ "automationControl.enable": [{
31143
+ name: "deviceId",
31144
+ form: "single",
31145
+ optional: false
31146
+ }],
31147
+ "automationControl.trigger": [{
31148
+ name: "deviceId",
31149
+ form: "single",
31150
+ optional: false
31151
+ }],
31152
+ "battery.wakeForStream": [{
31153
+ name: "deviceId",
31154
+ form: "single",
31155
+ optional: false
31156
+ }],
31157
+ "brightness.setBrightness": [{
31158
+ name: "deviceId",
31159
+ form: "single",
31160
+ optional: false
31161
+ }],
31162
+ "button.press": [{
31163
+ name: "deviceId",
31164
+ form: "single",
31165
+ optional: false
31166
+ }],
31167
+ "cameraCredentials.getCredentials": [{
31168
+ name: "deviceId",
31169
+ form: "single",
31170
+ optional: false
31171
+ }],
31172
+ "cameraStreams.getBrokerStreams": [{
31173
+ name: "deviceId",
31174
+ form: "single",
31175
+ optional: false
31176
+ }],
31177
+ "cameraStreams.getCameraStreams": [{
31178
+ name: "deviceId",
31179
+ form: "single",
31180
+ optional: false
31181
+ }],
31182
+ "cameraStreams.getProfileRtspEntries": [{
31183
+ name: "deviceId",
31184
+ form: "single",
31185
+ optional: false
31186
+ }],
31187
+ "cameraStreams.getRtspEntries": [{
31188
+ name: "deviceId",
31189
+ form: "single",
31190
+ optional: false
31191
+ }],
31192
+ "cameraStreams.pickStream": [{
31193
+ name: "deviceId",
31194
+ form: "single",
31195
+ optional: false
31196
+ }],
31197
+ "climateControl.setFanMode": [{
31198
+ name: "deviceId",
31199
+ form: "single",
31200
+ optional: false
31201
+ }],
31202
+ "climateControl.setMode": [{
31203
+ name: "deviceId",
31204
+ form: "single",
31205
+ optional: false
31206
+ }],
31207
+ "climateControl.setPreset": [{
31208
+ name: "deviceId",
31209
+ form: "single",
31210
+ optional: false
31211
+ }],
31212
+ "climateControl.setSwingHorizontal": [{
31213
+ name: "deviceId",
31214
+ form: "single",
31215
+ optional: false
31216
+ }],
31217
+ "climateControl.setSwingVertical": [{
31218
+ name: "deviceId",
31219
+ form: "single",
31220
+ optional: false
31221
+ }],
31222
+ "climateControl.setTarget": [{
31223
+ name: "deviceId",
31224
+ form: "single",
31225
+ optional: false
31226
+ }],
31227
+ "climateControl.setTargetHumidity": [{
31228
+ name: "deviceId",
31229
+ form: "single",
31230
+ optional: false
31231
+ }],
31232
+ "climateControl.setTargetRange": [{
31233
+ name: "deviceId",
31234
+ form: "single",
31235
+ optional: false
31236
+ }],
31237
+ "color.setColor": [{
31238
+ name: "deviceId",
31239
+ form: "single",
31240
+ optional: false
31241
+ }],
31242
+ "consumables.reset": [{
31243
+ name: "deviceId",
31244
+ form: "single",
31245
+ optional: false
31246
+ }],
31247
+ "control.setValue": [{
31248
+ name: "deviceId",
31249
+ form: "single",
31250
+ optional: false
31251
+ }],
31252
+ "cover.close": [{
31253
+ name: "deviceId",
31254
+ form: "single",
31255
+ optional: false
31256
+ }],
31257
+ "cover.open": [{
31258
+ name: "deviceId",
31259
+ form: "single",
31260
+ optional: false
31261
+ }],
31262
+ "cover.setPosition": [{
31263
+ name: "deviceId",
31264
+ form: "single",
31265
+ optional: false
31266
+ }],
31267
+ "cover.setTiltPosition": [{
31268
+ name: "deviceId",
31269
+ form: "single",
31270
+ optional: false
31271
+ }],
31272
+ "cover.stop": [{
31273
+ name: "deviceId",
31274
+ form: "single",
31275
+ optional: false
31276
+ }],
31277
+ "dayNight.getOptions": [{
31278
+ name: "deviceId",
31279
+ form: "single",
31280
+ optional: false
31281
+ }],
31282
+ "dayNight.setSettings": [{
31283
+ name: "deviceId",
31284
+ form: "single",
31285
+ optional: false
31286
+ }],
31287
+ "decoder.createSession": [{
31288
+ name: "deviceId",
31289
+ form: "single",
31290
+ optional: true
31291
+ }],
31292
+ "deviceAdoption.release": [{
31293
+ name: "camDeviceId",
31294
+ form: "single",
31295
+ optional: false
31296
+ }],
31297
+ "deviceAdoption.resync": [{
31298
+ name: "camDeviceId",
31299
+ form: "single",
31300
+ optional: false
31301
+ }],
31302
+ "deviceDiscovery.adoptDevice": [{
31303
+ name: "deviceId",
31304
+ form: "single",
31305
+ optional: false
31306
+ }],
31307
+ "deviceDiscovery.listDiscovered": [{
31308
+ name: "deviceId",
31309
+ form: "single",
31310
+ optional: false
31311
+ }],
31312
+ "deviceDiscovery.refreshDiscovery": [{
31313
+ name: "deviceId",
31314
+ form: "single",
31315
+ optional: false
31316
+ }],
31317
+ "deviceDiscovery.releaseDevice": [{
31318
+ name: "childDeviceId",
31319
+ form: "single",
31320
+ optional: false
31321
+ }, {
31322
+ name: "deviceId",
31323
+ form: "single",
31324
+ optional: false
31325
+ }],
31326
+ "deviceManager.adoptionRelease": [{
31327
+ name: "camDeviceId",
31328
+ form: "single",
31329
+ optional: false
31330
+ }],
31331
+ "deviceManager.adoptionResync": [{
31332
+ name: "camDeviceId",
31333
+ form: "single",
31334
+ optional: false
31335
+ }],
31336
+ "deviceManager.applyInitialMeta": [{
31337
+ name: "deviceId",
31338
+ form: "single",
31339
+ optional: false
31340
+ }, {
31341
+ name: "linkDeviceId",
31342
+ form: "single",
31343
+ optional: true
31344
+ }],
31345
+ "deviceManager.disable": [{
31346
+ name: "deviceId",
31347
+ form: "single",
31348
+ optional: false
31349
+ }],
31350
+ "deviceManager.enable": [{
31351
+ name: "deviceId",
31352
+ form: "single",
31353
+ optional: false
31354
+ }],
31355
+ "deviceManager.getBindings": [{
31356
+ name: "deviceId",
31357
+ form: "single",
31358
+ optional: false
31359
+ }],
31360
+ "deviceManager.getChildren": [{
31361
+ name: "parentDeviceId",
31362
+ form: "single",
31363
+ optional: false
31364
+ }],
31365
+ "deviceManager.getConfigSchema": [{
31366
+ name: "deviceId",
31367
+ form: "single",
31368
+ optional: false
31369
+ }],
31370
+ "deviceManager.getDevice": [{
31371
+ name: "deviceId",
31372
+ form: "single",
31373
+ optional: false
31374
+ }],
31375
+ "deviceManager.getDeviceAggregate": [{
31376
+ name: "deviceId",
31377
+ form: "single",
31378
+ optional: false
31379
+ }],
31380
+ "deviceManager.getDeviceLiveInfoAggregate": [{
31381
+ name: "deviceId",
31382
+ form: "single",
31383
+ optional: false
31384
+ }],
31385
+ "deviceManager.getDeviceSettingsAggregate": [{
31386
+ name: "deviceId",
31387
+ form: "single",
31388
+ optional: false
31389
+ }],
31390
+ "deviceManager.getDeviceStatusAggregate": [{
31391
+ name: "deviceId",
31392
+ form: "single",
31393
+ optional: false
31394
+ }],
31395
+ "deviceManager.getDeviceStatusAggregateBatch": [{
31396
+ name: "deviceIds",
31397
+ form: "array",
31398
+ optional: false
31399
+ }],
31400
+ "deviceManager.getLinkedDevices": [{
31401
+ name: "deviceId",
31402
+ form: "single",
31403
+ optional: false
31404
+ }],
31405
+ "deviceManager.getSettingsSchema": [{
31406
+ name: "deviceId",
31407
+ form: "single",
31408
+ optional: false
31409
+ }],
31410
+ "deviceManager.getStreamProfileMap": [{
31411
+ name: "deviceId",
31412
+ form: "single",
31413
+ optional: false
31414
+ }],
31415
+ "deviceManager.getStreamSources": [{
31416
+ name: "deviceId",
31417
+ form: "single",
31418
+ optional: false
31419
+ }],
31420
+ "deviceManager.getWireableFields": [{
31421
+ name: "deviceId",
31422
+ form: "single",
31423
+ optional: false
31424
+ }],
31425
+ "deviceManager.loadConfig": [{
31426
+ name: "deviceId",
31427
+ form: "single",
31428
+ optional: false
31429
+ }],
31430
+ "deviceManager.loadMeta": [{
31431
+ name: "deviceId",
31432
+ form: "single",
31433
+ optional: false
31434
+ }],
31435
+ "deviceManager.loadRuntimeState": [{
31436
+ name: "deviceId",
31437
+ form: "single",
31438
+ optional: false
31439
+ }],
31440
+ "deviceManager.persistConfig": [{
31441
+ name: "deviceId",
31442
+ form: "single",
31443
+ optional: false
31444
+ }],
31445
+ "deviceManager.probeStreams": [{
31446
+ name: "deviceId",
31447
+ form: "single",
31448
+ optional: false
31449
+ }],
31450
+ "deviceManager.registerDevice": [{
31451
+ name: "parentDeviceId",
31452
+ form: "single",
31453
+ optional: true
31454
+ }],
31455
+ "deviceManager.remove": [{
31456
+ name: "deviceId",
31457
+ form: "single",
31458
+ optional: false
31459
+ }],
31460
+ "deviceManager.removeDevice": [{
31461
+ name: "deviceId",
31462
+ form: "single",
31463
+ optional: false
31464
+ }],
31465
+ "deviceManager.runDeviceAction": [{
31466
+ name: "deviceId",
31467
+ form: "single",
31468
+ optional: false
31469
+ }],
31470
+ "deviceManager.setChildLayout": [{
31471
+ name: "deviceId",
31472
+ form: "single",
31473
+ optional: false
31474
+ }],
31475
+ "deviceManager.setDisabled": [{
31476
+ name: "deviceId",
31477
+ form: "single",
31478
+ optional: false
31479
+ }],
31480
+ "deviceManager.setDisplay": [{
31481
+ name: "deviceId",
31482
+ form: "single",
31483
+ optional: false
31484
+ }],
31485
+ "deviceManager.setIntegrationId": [{
31486
+ name: "deviceId",
31487
+ form: "single",
31488
+ optional: false
31489
+ }],
31490
+ "deviceManager.setLinkDeviceId": [{
31491
+ name: "deviceId",
31492
+ form: "single",
31493
+ optional: false
31494
+ }, {
31495
+ name: "linkDeviceId",
31496
+ form: "single",
31497
+ optional: true
31498
+ }],
31499
+ "deviceManager.setLocation": [{
31500
+ name: "deviceId",
31501
+ form: "single",
31502
+ optional: false
31503
+ }],
31504
+ "deviceManager.setMetadata": [{
31505
+ name: "deviceId",
31506
+ form: "single",
31507
+ optional: false
31508
+ }],
31509
+ "deviceManager.setName": [{
31510
+ name: "deviceId",
31511
+ form: "single",
31512
+ optional: false
31513
+ }],
31514
+ "deviceManager.setPrimaryChildEntityId": [{
31515
+ name: "deviceId",
31516
+ form: "single",
31517
+ optional: false
31518
+ }],
31519
+ "deviceManager.setRole": [{
31520
+ name: "deviceId",
31521
+ form: "single",
31522
+ optional: false
31523
+ }],
31524
+ "deviceManager.setStreamProfileMap": [{
31525
+ name: "deviceId",
31526
+ form: "single",
31527
+ optional: false
31528
+ }],
31529
+ "deviceManager.setType": [{
31530
+ name: "deviceId",
31531
+ form: "single",
31532
+ optional: false
31533
+ }],
31534
+ "deviceManager.setWrapperActive": [{
31535
+ name: "deviceId",
31536
+ form: "single",
31537
+ optional: false
31538
+ }],
31539
+ "deviceManager.testField": [{
31540
+ name: "deviceId",
31541
+ form: "single",
31542
+ optional: false
31543
+ }],
31544
+ "deviceManager.updateConfig": [{
31545
+ name: "deviceId",
31546
+ form: "single",
31547
+ optional: false
31548
+ }],
31549
+ "deviceManager.updateDeviceField": [{
31550
+ name: "deviceId",
31551
+ form: "single",
31552
+ optional: false
31553
+ }],
31554
+ "deviceManager.updateDeviceFieldsBatch": [{
31555
+ name: "deviceId",
31556
+ form: "single",
31557
+ optional: false
31558
+ }],
31559
+ "deviceOps.getConfigEntries": [{
31560
+ name: "deviceId",
31561
+ form: "single",
31562
+ optional: false
31563
+ }],
31564
+ "deviceOps.getRawState": [{
31565
+ name: "deviceId",
31566
+ form: "single",
31567
+ optional: false
31568
+ }],
31569
+ "deviceOps.getSettingsSchema": [{
31570
+ name: "deviceId",
31571
+ form: "single",
31572
+ optional: false
31573
+ }],
31574
+ "deviceOps.getStreamSources": [{
31575
+ name: "deviceId",
31576
+ form: "single",
31577
+ optional: false
31578
+ }],
31579
+ "deviceOps.removeDevice": [{
31580
+ name: "deviceId",
31581
+ form: "single",
31582
+ optional: false
31583
+ }],
31584
+ "deviceOps.runAction": [{
31585
+ name: "deviceId",
31586
+ form: "single",
31587
+ optional: false
31588
+ }],
31589
+ "deviceOps.setConfig": [{
31590
+ name: "deviceId",
31591
+ form: "single",
31592
+ optional: false
31593
+ }],
31594
+ "deviceState.getCapSlice": [{
31595
+ name: "deviceId",
31596
+ form: "single",
31597
+ optional: false
31598
+ }],
31599
+ "deviceState.getSnapshot": [{
31600
+ name: "deviceId",
31601
+ form: "single",
31602
+ optional: false
31603
+ }],
31604
+ "deviceState.setCapSlice": [{
31605
+ name: "deviceId",
31606
+ form: "single",
31607
+ optional: false
31608
+ }],
31609
+ "events.getEventClipUrl": [{
31610
+ name: "deviceId",
31611
+ form: "single",
31612
+ optional: false
31613
+ }],
31614
+ "events.getEvents": [{
31615
+ name: "deviceId",
31616
+ form: "single",
31617
+ optional: false
31618
+ }],
31619
+ "events.getEventThumbnail": [{
31620
+ name: "deviceId",
31621
+ form: "single",
31622
+ optional: false
31623
+ }],
31624
+ "faceGallery.getFaceByTrack": [{
31625
+ name: "deviceId",
31626
+ form: "single",
31627
+ optional: false
31628
+ }],
31629
+ "faceGallery.listRecentFaces": [{
31630
+ name: "deviceId",
31631
+ form: "single",
31632
+ optional: true
31633
+ }],
31634
+ "fanControl.setDirection": [{
31635
+ name: "deviceId",
31636
+ form: "single",
31637
+ optional: false
31638
+ }],
31639
+ "fanControl.setOscillating": [{
31640
+ name: "deviceId",
31641
+ form: "single",
31642
+ optional: false
31643
+ }],
31644
+ "fanControl.setPercentage": [{
31645
+ name: "deviceId",
31646
+ form: "single",
31647
+ optional: false
31648
+ }],
31649
+ "fanControl.setPreset": [{
31650
+ name: "deviceId",
31651
+ form: "single",
31652
+ optional: false
31653
+ }],
31654
+ "humidifier.setMode": [{
31655
+ name: "deviceId",
31656
+ form: "single",
31657
+ optional: false
31658
+ }],
31659
+ "humidifier.setOn": [{
31660
+ name: "deviceId",
31661
+ form: "single",
31662
+ optional: false
31663
+ }],
31664
+ "humidifier.setTargetHumidity": [{
31665
+ name: "deviceId",
31666
+ form: "single",
31667
+ optional: false
31668
+ }],
31669
+ "imageSettings.getOptions": [{
31670
+ name: "deviceId",
31671
+ form: "single",
31672
+ optional: false
31673
+ }],
31674
+ "imageSettings.setSettings": [{
31675
+ name: "deviceId",
31676
+ form: "single",
31677
+ optional: false
31678
+ }],
31679
+ "intercom.endTalkSession": [{
31680
+ name: "deviceId",
31681
+ form: "single",
31682
+ optional: false
31683
+ }],
31684
+ "intercom.handleAnswer": [{
31685
+ name: "deviceId",
31686
+ form: "single",
31687
+ optional: false
31688
+ }],
31689
+ "intercom.pushTalkAudio": [{
31690
+ name: "deviceId",
31691
+ form: "single",
31692
+ optional: false
31693
+ }],
31694
+ "intercom.startSession": [{
31695
+ name: "deviceId",
31696
+ form: "single",
31697
+ optional: false
31698
+ }],
31699
+ "intercom.startTalkSession": [{
31700
+ name: "deviceId",
31701
+ form: "single",
31702
+ optional: false
31703
+ }],
31704
+ "intercom.stopSession": [{
31705
+ name: "deviceId",
31706
+ form: "single",
31707
+ optional: false
31708
+ }],
31709
+ "lawnMowerControl.dock": [{
31710
+ name: "deviceId",
31711
+ form: "single",
31712
+ optional: false
31713
+ }],
31714
+ "lawnMowerControl.pause": [{
31715
+ name: "deviceId",
31716
+ form: "single",
31717
+ optional: false
31718
+ }],
31719
+ "lawnMowerControl.startMowing": [{
31720
+ name: "deviceId",
31721
+ form: "single",
31722
+ optional: false
31723
+ }],
31724
+ "lockControl.lock": [{
31725
+ name: "deviceId",
31726
+ form: "single",
31727
+ optional: false
31728
+ }],
31729
+ "lockControl.open": [{
31730
+ name: "deviceId",
31731
+ form: "single",
31732
+ optional: false
31733
+ }],
31734
+ "lockControl.unlock": [{
31735
+ name: "deviceId",
31736
+ form: "single",
31737
+ optional: false
31738
+ }],
31739
+ "mediaPlayer.next": [{
31740
+ name: "deviceId",
31741
+ form: "single",
31742
+ optional: false
31743
+ }],
31744
+ "mediaPlayer.pause": [{
31745
+ name: "deviceId",
31746
+ form: "single",
31747
+ optional: false
31748
+ }],
31749
+ "mediaPlayer.play": [{
31750
+ name: "deviceId",
31751
+ form: "single",
31752
+ optional: false
31753
+ }],
31754
+ "mediaPlayer.playMedia": [{
31755
+ name: "deviceId",
31756
+ form: "single",
31757
+ optional: false
31758
+ }],
31759
+ "mediaPlayer.previous": [{
31760
+ name: "deviceId",
31761
+ form: "single",
31762
+ optional: false
31763
+ }],
31764
+ "mediaPlayer.seek": [{
31765
+ name: "deviceId",
31766
+ form: "single",
31767
+ optional: false
31768
+ }],
31769
+ "mediaPlayer.selectSource": [{
31770
+ name: "deviceId",
31771
+ form: "single",
31772
+ optional: false
31773
+ }],
31774
+ "mediaPlayer.setMute": [{
31775
+ name: "deviceId",
31776
+ form: "single",
31777
+ optional: false
31778
+ }],
31779
+ "mediaPlayer.setRepeat": [{
31780
+ name: "deviceId",
31781
+ form: "single",
31782
+ optional: false
31783
+ }],
31784
+ "mediaPlayer.setShuffle": [{
31785
+ name: "deviceId",
31786
+ form: "single",
31787
+ optional: false
31788
+ }],
31789
+ "mediaPlayer.setVolume": [{
31790
+ name: "deviceId",
31791
+ form: "single",
31792
+ optional: false
31793
+ }],
31794
+ "mediaPlayer.stop": [{
31795
+ name: "deviceId",
31796
+ form: "single",
31797
+ optional: false
31798
+ }],
31799
+ "motion.isDetected": [{
31800
+ name: "deviceId",
31801
+ form: "single",
31802
+ optional: false
31803
+ }],
31804
+ "motionDetection.analyze": [{
31805
+ name: "deviceId",
31806
+ form: "single",
31807
+ optional: false
31808
+ }],
31809
+ "motionDetection.removeCamera": [{
31810
+ name: "deviceId",
31811
+ form: "single",
31812
+ optional: false
31813
+ }],
31814
+ "motionTrigger.setMotionTrigger": [{
31815
+ name: "deviceId",
31816
+ form: "single",
31817
+ optional: false
31818
+ }],
31819
+ "motionZones.getOptions": [{
31820
+ name: "deviceId",
31821
+ form: "single",
31822
+ optional: false
31823
+ }],
31824
+ "motionZones.setZone": [{
31825
+ name: "deviceId",
31826
+ form: "single",
31827
+ optional: false
31828
+ }],
31829
+ "nativeObjectDetection.setEnabled": [{
31830
+ name: "deviceId",
31831
+ form: "single",
31832
+ optional: false
31833
+ }],
31834
+ "networkQuality.getDeviceStats": [{
31835
+ name: "deviceId",
31836
+ form: "single",
31837
+ optional: false
31838
+ }],
31839
+ "networkQuality.reportClientStats": [{
31840
+ name: "deviceId",
31841
+ form: "single",
31842
+ optional: false
31843
+ }],
31844
+ "notificationRules.setDeviceMuted": [{
31845
+ name: "deviceId",
31846
+ form: "single",
31847
+ optional: false
31848
+ }],
31849
+ "notifier.cancel": [{
31850
+ name: "deviceId",
31851
+ form: "single",
31852
+ optional: false
31853
+ }],
31854
+ "notifier.send": [{
31855
+ name: "deviceId",
31856
+ form: "single",
31857
+ optional: false
31858
+ }],
31859
+ "osd.setOverlay": [{
31860
+ name: "deviceId",
31861
+ form: "single",
31862
+ optional: false
31863
+ }],
31864
+ "osdManager.clearSlotBinding": [{
31865
+ name: "deviceId",
31866
+ form: "single",
31867
+ optional: false
31868
+ }],
31869
+ "osdManager.copyDeviceConfiguration": [{
31870
+ name: "sourceDeviceId",
31871
+ form: "single",
31872
+ optional: false
31873
+ }, {
31874
+ name: "targetDeviceId",
31875
+ form: "single",
31876
+ optional: false
31877
+ }],
31878
+ "osdManager.getDeviceOsd": [{
31879
+ name: "deviceId",
31880
+ form: "single",
31881
+ optional: false
31882
+ }],
31883
+ "osdManager.getSourceCatalog": [{
31884
+ name: "deviceId",
31885
+ form: "single",
31886
+ optional: false
31887
+ }],
31888
+ "osdManager.previewSlot": [{
31889
+ name: "deviceId",
31890
+ form: "single",
31891
+ optional: false
31892
+ }],
31893
+ "osdManager.renderDevice": [{
31894
+ name: "deviceId",
31895
+ form: "single",
31896
+ optional: false
31897
+ }],
31898
+ "osdManager.setSlotBinding": [{
31899
+ name: "deviceId",
31900
+ form: "single",
31901
+ optional: false
31902
+ }],
31903
+ "petFeeder.callPet": [{
31904
+ name: "deviceId",
31905
+ form: "single",
31906
+ optional: false
31907
+ }],
31908
+ "petFeeder.cancelFeed": [{
31909
+ name: "deviceId",
31910
+ form: "single",
31911
+ optional: false
31912
+ }],
31913
+ "petFeeder.feed": [{
31914
+ name: "deviceId",
31915
+ form: "single",
31916
+ optional: false
31917
+ }],
31918
+ "petFeeder.markFoodReplenished": [{
31919
+ name: "deviceId",
31920
+ form: "single",
31921
+ optional: false
31922
+ }],
31923
+ "petFeeder.playSound": [{
31924
+ name: "deviceId",
31925
+ form: "single",
31926
+ optional: false
31927
+ }],
31928
+ "petFeeder.resetDesiccant": [{
31929
+ name: "deviceId",
31930
+ form: "single",
31931
+ optional: false
31932
+ }],
31933
+ "petFeeder.setChildLock": [{
31934
+ name: "deviceId",
31935
+ form: "single",
31936
+ optional: false
31937
+ }],
31938
+ "petFeeder.setFeedSound": [{
31939
+ name: "deviceId",
31940
+ form: "single",
31941
+ optional: false
31942
+ }],
31943
+ "petFeeder.setIndicatorLight": [{
31944
+ name: "deviceId",
31945
+ form: "single",
31946
+ optional: false
31947
+ }],
31948
+ "petFeeder.setVolume": [{
31949
+ name: "deviceId",
31950
+ form: "single",
31951
+ optional: false
31952
+ }],
31953
+ "pipelineAnalytics.clearTracks": [{
31954
+ name: "deviceId",
31955
+ form: "single",
31956
+ optional: false
31957
+ }],
31958
+ "pipelineAnalytics.completeRetrainTrack": [{
31959
+ name: "deviceId",
31960
+ form: "single",
31961
+ optional: false
31962
+ }],
31963
+ "pipelineAnalytics.deleteDeviceEvents": [{
31964
+ name: "deviceId",
31965
+ form: "single",
31966
+ optional: false
31967
+ }],
31968
+ "pipelineAnalytics.deleteTracks": [{
31969
+ name: "deviceId",
31970
+ form: "single",
31971
+ optional: false
31972
+ }],
31973
+ "pipelineAnalytics.deselectRetrainFrame": [{
31974
+ name: "deviceId",
31975
+ form: "single",
31976
+ optional: false
31977
+ }],
31978
+ "pipelineAnalytics.getActiveTracks": [{
31979
+ name: "deviceId",
31980
+ form: "single",
31981
+ optional: false
31982
+ }],
31983
+ "pipelineAnalytics.getAudioEvents": [{
31984
+ name: "deviceId",
31985
+ form: "single",
31986
+ optional: false
31987
+ }],
31988
+ "pipelineAnalytics.getEventDensity": [{
31989
+ name: "deviceId",
31990
+ form: "single",
31991
+ optional: false
31992
+ }],
31993
+ "pipelineAnalytics.getKeyEvents": [{
31994
+ name: "deviceId",
31995
+ form: "single",
31996
+ optional: false
31997
+ }],
31998
+ "pipelineAnalytics.getMotionEvents": [{
31999
+ name: "deviceId",
32000
+ form: "single",
32001
+ optional: false
32002
+ }],
32003
+ "pipelineAnalytics.getObjectEvents": [{
32004
+ name: "deviceId",
32005
+ form: "single",
32006
+ optional: false
32007
+ }],
32008
+ "pipelineAnalytics.getRetrainExportUrl": [{
32009
+ name: "deviceIds",
32010
+ form: "array",
32011
+ optional: true
32012
+ }],
32013
+ "pipelineAnalytics.getSensorEvents": [{
32014
+ name: "deviceId",
32015
+ form: "single",
32016
+ optional: false
32017
+ }],
32018
+ "pipelineAnalytics.getTrack": [{
32019
+ name: "deviceId",
32020
+ form: "single",
32021
+ optional: false
32022
+ }],
32023
+ "pipelineAnalytics.getTrainingExportSummary": [{
32024
+ name: "deviceIds",
32025
+ form: "array",
32026
+ optional: true
32027
+ }],
32028
+ "pipelineAnalytics.getTrainingExportUrl": [{
32029
+ name: "deviceIds",
32030
+ form: "array",
32031
+ optional: true
32032
+ }],
32033
+ "pipelineAnalytics.listEventKinds": [{
32034
+ name: "deviceId",
32035
+ form: "single",
32036
+ optional: false
32037
+ }],
32038
+ "pipelineAnalytics.listEventKindsBatch": [{
32039
+ name: "deviceIds",
32040
+ form: "array",
32041
+ optional: false
32042
+ }],
32043
+ "pipelineAnalytics.listOpsLog": [{
32044
+ name: "deviceId",
32045
+ form: "single",
32046
+ optional: true
32047
+ }],
32048
+ "pipelineAnalytics.listRecentTracks": [{
32049
+ name: "deviceIds",
32050
+ form: "array",
32051
+ optional: false
32052
+ }],
32053
+ "pipelineAnalytics.listRetrainStaging": [{
32054
+ name: "deviceIds",
32055
+ form: "array",
32056
+ optional: true
32057
+ }],
32058
+ "pipelineAnalytics.listTracks": [{
32059
+ name: "deviceId",
32060
+ form: "single",
32061
+ optional: false
32062
+ }],
32063
+ "pipelineAnalytics.proposeRetrainAnnotations": [{
32064
+ name: "deviceId",
32065
+ form: "single",
32066
+ optional: false
32067
+ }],
32068
+ "pipelineAnalytics.pruneEventsBefore": [{
32069
+ name: "deviceId",
32070
+ form: "single",
32071
+ optional: false
32072
+ }],
32073
+ "pipelineAnalytics.pruneTracksBefore": [{
32074
+ name: "deviceId",
32075
+ form: "single",
32076
+ optional: false
32077
+ }],
32078
+ "pipelineAnalytics.rebuildObjectEmbeddings": [{
32079
+ name: "deviceId",
32080
+ form: "single",
32081
+ optional: true
32082
+ }],
32083
+ "pipelineAnalytics.restageRetrainTrack": [{
32084
+ name: "deviceId",
32085
+ form: "single",
32086
+ optional: false
32087
+ }],
32088
+ "pipelineAnalytics.saveRetrainAnnotations": [{
32089
+ name: "deviceId",
32090
+ form: "single",
32091
+ optional: false
32092
+ }],
32093
+ "pipelineAnalytics.searchObjectEvents": [{
32094
+ name: "deviceId",
32095
+ form: "single",
32096
+ optional: true
32097
+ }],
32098
+ "pipelineAnalytics.selectRetrainFrames": [{
32099
+ name: "deviceId",
32100
+ form: "single",
32101
+ optional: false
32102
+ }],
32103
+ "pipelineAnalytics.setTrackFlags": [{
32104
+ name: "deviceId",
32105
+ form: "single",
32106
+ optional: false
32107
+ }],
32108
+ "pipelineAnalytics.wipeAllAnalytics": [{
32109
+ name: "deviceId",
32110
+ form: "single",
32111
+ optional: false
32112
+ }],
32113
+ "pipelineExecutor.runPipeline": [{
32114
+ name: "deviceId",
32115
+ form: "single",
32116
+ optional: true
32117
+ }],
32118
+ "pipelineExecutor.runPipelineBatch": [{
32119
+ name: "deviceId",
32120
+ form: "single",
32121
+ optional: true
32122
+ }],
32123
+ "pipelineOrchestrator.assignAudio": [{
32124
+ name: "deviceId",
32125
+ form: "single",
32126
+ optional: false
32127
+ }],
32128
+ "pipelineOrchestrator.assignPipeline": [{
32129
+ name: "deviceId",
32130
+ form: "single",
32131
+ optional: false
32132
+ }],
32133
+ "pipelineOrchestrator.getAudioAssignment": [{
32134
+ name: "deviceId",
32135
+ form: "single",
32136
+ optional: false
32137
+ }],
32138
+ "pipelineOrchestrator.getCameraMetrics": [{
32139
+ name: "deviceId",
32140
+ form: "single",
32141
+ optional: false
32142
+ }],
32143
+ "pipelineOrchestrator.getCameraSettings": [{
32144
+ name: "deviceId",
32145
+ form: "single",
32146
+ optional: false
32147
+ }],
32148
+ "pipelineOrchestrator.getCameraStatus": [{
32149
+ name: "deviceId",
32150
+ form: "single",
32151
+ optional: false
32152
+ }],
32153
+ "pipelineOrchestrator.getCameraStatuses": [{
32154
+ name: "deviceIds",
32155
+ form: "array",
32156
+ optional: true
32157
+ }],
32158
+ "pipelineOrchestrator.getCameraStepOverrides": [{
32159
+ name: "deviceId",
32160
+ form: "single",
32161
+ optional: false
32162
+ }],
32163
+ "pipelineOrchestrator.getCameraSwitches": [{
32164
+ name: "deviceId",
32165
+ form: "single",
32166
+ optional: false
32167
+ }],
32168
+ "pipelineOrchestrator.getPipelineAssignment": [{
32169
+ name: "deviceId",
32170
+ form: "single",
32171
+ optional: false
32172
+ }],
32173
+ "pipelineOrchestrator.getPipelineDevicePin": [{
32174
+ name: "deviceId",
32175
+ form: "single",
32176
+ optional: false
32177
+ }],
32178
+ "pipelineOrchestrator.resolvePipeline": [{
32179
+ name: "deviceId",
32180
+ form: "single",
32181
+ optional: false
32182
+ }],
32183
+ "pipelineOrchestrator.setCameraPipelineForAgent": [{
32184
+ name: "deviceId",
32185
+ form: "single",
32186
+ optional: false
32187
+ }],
32188
+ "pipelineOrchestrator.setCameraStepOverride": [{
32189
+ name: "deviceId",
32190
+ form: "single",
32191
+ optional: false
32192
+ }],
32193
+ "pipelineOrchestrator.setCameraStepToggle": [{
32194
+ name: "deviceId",
32195
+ form: "single",
32196
+ optional: false
32197
+ }],
32198
+ "pipelineOrchestrator.setCameraSwitch": [{
32199
+ name: "deviceId",
32200
+ form: "single",
32201
+ optional: false
32202
+ }],
32203
+ "pipelineOrchestrator.setPipelineDevicePin": [{
32204
+ name: "deviceId",
32205
+ form: "single",
32206
+ optional: false
32207
+ }],
32208
+ "pipelineOrchestrator.unassignAudio": [{
32209
+ name: "deviceId",
32210
+ form: "single",
32211
+ optional: false
32212
+ }],
32213
+ "pipelineOrchestrator.unassignPipeline": [{
32214
+ name: "deviceId",
32215
+ form: "single",
32216
+ optional: false
32217
+ }],
32218
+ "pipelineRunner.attachCamera": [{
32219
+ name: "deviceId",
32220
+ form: "single",
32221
+ optional: false
32222
+ }],
32223
+ "pipelineRunner.detachCamera": [{
32224
+ name: "deviceId",
32225
+ form: "single",
32226
+ optional: false
32227
+ }],
32228
+ "pipelineRunner.getCameraMetrics": [{
32229
+ name: "deviceId",
32230
+ form: "single",
32231
+ optional: false
32232
+ }],
32233
+ "pipelineRunner.reportMotion": [{
32234
+ name: "deviceId",
32235
+ form: "single",
32236
+ optional: false
32237
+ }],
32238
+ "pipelineRunner.runDetailSubtree": [{
32239
+ name: "deviceId",
32240
+ form: "single",
32241
+ optional: false
32242
+ }],
32243
+ "pipelineRunner.runStatelessStep": [{
32244
+ name: "sourceDeviceId",
32245
+ form: "single",
32246
+ optional: false
32247
+ }],
32248
+ "plateGallery.getPlateByTrack": [{
32249
+ name: "deviceId",
32250
+ form: "single",
32251
+ optional: false
32252
+ }],
32253
+ "plateGallery.listPlates": [{
32254
+ name: "deviceId",
32255
+ form: "single",
32256
+ optional: true
32257
+ }],
32258
+ "privacyMask.getOptions": [{
32259
+ name: "deviceId",
32260
+ form: "single",
32261
+ optional: false
32262
+ }],
32263
+ "privacyMask.setAudioEnabled": [{
32264
+ name: "deviceId",
32265
+ form: "single",
32266
+ optional: false
32267
+ }],
32268
+ "privacyMask.setMask": [{
32269
+ name: "deviceId",
32270
+ form: "single",
32271
+ optional: false
32272
+ }],
32273
+ "ptz.continuousMove": [{
32274
+ name: "deviceId",
32275
+ form: "single",
32276
+ optional: false
32277
+ }],
32278
+ "ptz.deletePreset": [{
32279
+ name: "deviceId",
32280
+ form: "single",
32281
+ optional: false
32282
+ }],
32283
+ "ptz.getOptions": [{
32284
+ name: "deviceId",
32285
+ form: "single",
32286
+ optional: false
32287
+ }],
32288
+ "ptz.getPosition": [{
32289
+ name: "deviceId",
32290
+ form: "single",
32291
+ optional: false
32292
+ }],
32293
+ "ptz.getPresets": [{
32294
+ name: "deviceId",
32295
+ form: "single",
32296
+ optional: false
32297
+ }],
32298
+ "ptz.goHome": [{
32299
+ name: "deviceId",
32300
+ form: "single",
32301
+ optional: false
32302
+ }],
32303
+ "ptz.goToPreset": [{
32304
+ name: "deviceId",
32305
+ form: "single",
32306
+ optional: false
32307
+ }],
32308
+ "ptz.move": [{
32309
+ name: "deviceId",
32310
+ form: "single",
32311
+ optional: false
32312
+ }],
32313
+ "ptz.savePreset": [{
32314
+ name: "deviceId",
32315
+ form: "single",
32316
+ optional: false
32317
+ }],
32318
+ "ptz.setAutofocus": [{
32319
+ name: "deviceId",
32320
+ form: "single",
32321
+ optional: false
32322
+ }],
32323
+ "ptz.stop": [{
32324
+ name: "deviceId",
32325
+ form: "single",
32326
+ optional: false
32327
+ }],
32328
+ "ptzAutotrack.getSettings": [{
32329
+ name: "deviceId",
32330
+ form: "single",
32331
+ optional: false
32332
+ }],
32333
+ "ptzAutotrack.getStatus": [{
32334
+ name: "deviceId",
32335
+ form: "single",
32336
+ optional: false
32337
+ }],
32338
+ "ptzAutotrack.setEnabled": [{
32339
+ name: "deviceId",
32340
+ form: "single",
32341
+ optional: false
32342
+ }],
32343
+ "ptzAutotrack.setSettings": [{
32344
+ name: "deviceId",
32345
+ form: "single",
32346
+ optional: false
32347
+ }],
32348
+ "reboot.reboot": [{
32349
+ name: "deviceId",
32350
+ form: "single",
32351
+ optional: false
32352
+ }],
32353
+ "recording.deleteFootprint": [{
32354
+ name: "deviceId",
32355
+ form: "single",
32356
+ optional: false
32357
+ }],
32358
+ "recording.getAvailability": [{
32359
+ name: "deviceId",
32360
+ form: "single",
32361
+ optional: false
32362
+ }],
32363
+ "recording.getDaysWithRecordings": [{
32364
+ name: "deviceId",
32365
+ form: "single",
32366
+ optional: false
32367
+ }],
32368
+ "recording.getDeviceConfig": [{
32369
+ name: "deviceId",
32370
+ form: "single",
32371
+ optional: false
32372
+ }],
32373
+ "recording.getPlaybackManifest": [{
32374
+ name: "deviceId",
32375
+ form: "single",
32376
+ optional: false
32377
+ }],
32378
+ "recording.listOpsLog": [{
32379
+ name: "deviceId",
32380
+ form: "single",
32381
+ optional: true
32382
+ }],
32383
+ "recording.locateSegment": [{
32384
+ name: "deviceId",
32385
+ form: "single",
32386
+ optional: false
32387
+ }],
32388
+ "recording.pruneFootage": [{
32389
+ name: "deviceId",
32390
+ form: "single",
32391
+ optional: false
32392
+ }],
32393
+ "recording.readGopBytes": [{
32394
+ name: "deviceId",
32395
+ form: "single",
32396
+ optional: false
32397
+ }],
32398
+ "recording.readSegmentBytes": [{
32399
+ name: "deviceId",
32400
+ form: "single",
32401
+ optional: false
32402
+ }],
32403
+ "recording.relocateFootage": [{
32404
+ name: "deviceId",
32405
+ form: "single",
32406
+ optional: true
32407
+ }],
32408
+ "recording.renderClip": [{
32409
+ name: "deviceId",
32410
+ form: "single",
32411
+ optional: false
32412
+ }],
32413
+ "recording.renderGif": [{
32414
+ name: "deviceId",
32415
+ form: "single",
32416
+ optional: false
32417
+ }],
32418
+ "recording.rescanStorage": [{
32419
+ name: "deviceId",
32420
+ form: "single",
32421
+ optional: false
32422
+ }],
32423
+ "recording.setDeviceConfig": [{
32424
+ name: "deviceId",
32425
+ form: "single",
32426
+ optional: false
32427
+ }],
32428
+ "recording.startStorageMigrationMove": [{
32429
+ name: "deviceId",
32430
+ form: "single",
32431
+ optional: true
32432
+ }],
32433
+ "recordingExport.createExport": [{
32434
+ name: "deviceId",
32435
+ form: "single",
32436
+ optional: false
32437
+ }],
32438
+ "recordingExport.listExports": [{
32439
+ name: "deviceId",
32440
+ form: "single",
32441
+ optional: true
32442
+ }],
32443
+ "sceneMonitor.captureReference": [{
32444
+ name: "deviceId",
32445
+ form: "single",
32446
+ optional: false
32447
+ }],
32448
+ "sceneMonitor.createScene": [{
32449
+ name: "deviceId",
32450
+ form: "single",
32451
+ optional: false
32452
+ }],
32453
+ "sceneMonitor.deleteReference": [{
32454
+ name: "deviceId",
32455
+ form: "single",
32456
+ optional: false
32457
+ }],
32458
+ "sceneMonitor.deleteScene": [{
32459
+ name: "deviceId",
32460
+ form: "single",
32461
+ optional: false
32462
+ }],
32463
+ "sceneMonitor.listScenes": [{
32464
+ name: "deviceId",
32465
+ form: "single",
32466
+ optional: false
32467
+ }],
32468
+ "sceneMonitor.recheckNow": [{
32469
+ name: "deviceId",
32470
+ form: "single",
32471
+ optional: false
32472
+ }],
32473
+ "sceneMonitor.resetScene": [{
32474
+ name: "deviceId",
32475
+ form: "single",
32476
+ optional: false
32477
+ }],
32478
+ "sceneMonitor.updateScene": [{
32479
+ name: "deviceId",
32480
+ form: "single",
32481
+ optional: false
32482
+ }],
32483
+ "scriptRunner.run": [{
32484
+ name: "deviceId",
32485
+ form: "single",
32486
+ optional: false
32487
+ }],
32488
+ "scriptRunner.stop": [{
32489
+ name: "deviceId",
32490
+ form: "single",
32491
+ optional: false
32492
+ }],
32493
+ "snapshot.getSnapshot": [{
32494
+ name: "deviceId",
32495
+ form: "single",
32496
+ optional: false
32497
+ }],
32498
+ "snapshot.getSnapshotOverview": [{
32499
+ name: "deviceIds",
32500
+ form: "array",
32501
+ optional: false
32502
+ }],
32503
+ "snapshot.invalidateCache": [{
32504
+ name: "deviceId",
32505
+ form: "single",
32506
+ optional: false
32507
+ }],
32508
+ "streamBroker.acquireEgressTranscode": [{
32509
+ name: "deviceId",
32510
+ form: "single",
32511
+ optional: false
32512
+ }],
32513
+ "streamBroker.assignProfile": [{
32514
+ name: "deviceId",
32515
+ form: "single",
32516
+ optional: false
32517
+ }],
32518
+ "streamBroker.getDeviceAudioMute": [{
32519
+ name: "deviceId",
32520
+ form: "single",
32521
+ optional: false
32522
+ }],
32523
+ "streamBroker.getStreamWithCodec": [{
32524
+ name: "deviceId",
32525
+ form: "single",
32526
+ optional: false
32527
+ }],
32528
+ "streamBroker.produceEventMedia": [{
32529
+ name: "deviceId",
32530
+ form: "single",
32531
+ optional: false
32532
+ }],
32533
+ "streamBroker.publishCameraStream": [{
32534
+ name: "deviceId",
32535
+ form: "single",
32536
+ optional: false
32537
+ }],
32538
+ "streamBroker.renderPreBufferClip": [{
32539
+ name: "deviceId",
32540
+ form: "single",
32541
+ optional: false
32542
+ }],
32543
+ "streamBroker.restartProfile": [{
32544
+ name: "deviceId",
32545
+ form: "single",
32546
+ optional: false
32547
+ }],
32548
+ "streamBroker.retractCameraStream": [{
32549
+ name: "deviceId",
32550
+ form: "single",
32551
+ optional: false
32552
+ }],
32553
+ "streamBroker.setDeviceAudioMute": [{
32554
+ name: "deviceId",
32555
+ form: "single",
32556
+ optional: false
32557
+ }],
32558
+ "streamBroker.unassignProfile": [{
32559
+ name: "deviceId",
32560
+ form: "single",
32561
+ optional: false
32562
+ }],
32563
+ "streamCatalog.getCatalog": [{
32564
+ name: "deviceId",
32565
+ form: "single",
32566
+ optional: false
32567
+ }],
32568
+ "streamParams.getConfigSchema": [{
32569
+ name: "deviceId",
32570
+ form: "single",
32571
+ optional: false
32572
+ }],
32573
+ "streamParams.getOptions": [{
32574
+ name: "deviceId",
32575
+ form: "single",
32576
+ optional: false
32577
+ }],
32578
+ "streamParams.setProfile": [{
32579
+ name: "deviceId",
32580
+ form: "single",
32581
+ optional: false
32582
+ }],
32583
+ "switch.setState": [{
32584
+ name: "deviceId",
32585
+ form: "single",
32586
+ optional: false
32587
+ }],
32588
+ "vacuumControl.locate": [{
32589
+ name: "deviceId",
32590
+ form: "single",
32591
+ optional: false
32592
+ }],
32593
+ "vacuumControl.pause": [{
32594
+ name: "deviceId",
32595
+ form: "single",
32596
+ optional: false
32597
+ }],
32598
+ "vacuumControl.returnToBase": [{
32599
+ name: "deviceId",
32600
+ form: "single",
32601
+ optional: false
32602
+ }],
32603
+ "vacuumControl.setFanSpeed": [{
32604
+ name: "deviceId",
32605
+ form: "single",
32606
+ optional: false
32607
+ }],
32608
+ "vacuumControl.start": [{
32609
+ name: "deviceId",
32610
+ form: "single",
32611
+ optional: false
32612
+ }],
32613
+ "vacuumControl.stop": [{
32614
+ name: "deviceId",
32615
+ form: "single",
32616
+ optional: false
32617
+ }],
32618
+ "valve.close": [{
32619
+ name: "deviceId",
32620
+ form: "single",
32621
+ optional: false
32622
+ }],
32623
+ "valve.open": [{
32624
+ name: "deviceId",
32625
+ form: "single",
32626
+ optional: false
32627
+ }],
32628
+ "valve.setPosition": [{
32629
+ name: "deviceId",
32630
+ form: "single",
32631
+ optional: false
32632
+ }],
32633
+ "valve.stop": [{
32634
+ name: "deviceId",
32635
+ form: "single",
32636
+ optional: false
32637
+ }],
32638
+ "videoclips.getClipPlayback": [{
32639
+ name: "deviceId",
32640
+ form: "single",
32641
+ optional: false
32642
+ }],
32643
+ "videoclips.listClips": [{
32644
+ name: "deviceId",
32645
+ form: "single",
32646
+ optional: false
32647
+ }],
32648
+ "waterHeater.setAway": [{
32649
+ name: "deviceId",
32650
+ form: "single",
32651
+ optional: false
32652
+ }],
32653
+ "waterHeater.setOperationMode": [{
32654
+ name: "deviceId",
32655
+ form: "single",
32656
+ optional: false
32657
+ }],
32658
+ "waterHeater.setTargetTemp": [{
32659
+ name: "deviceId",
32660
+ form: "single",
32661
+ optional: false
32662
+ }],
32663
+ "webrtcSession.addIceCandidate": [{
32664
+ name: "deviceId",
32665
+ form: "single",
32666
+ optional: false
32667
+ }],
32668
+ "webrtcSession.closeSession": [{
32669
+ name: "deviceId",
32670
+ form: "single",
32671
+ optional: false
32672
+ }],
32673
+ "webrtcSession.createSession": [{
32674
+ name: "deviceId",
32675
+ form: "single",
32676
+ optional: false
32677
+ }],
32678
+ "webrtcSession.getIceCandidates": [{
32679
+ name: "deviceId",
32680
+ form: "single",
32681
+ optional: false
32682
+ }],
32683
+ "webrtcSession.getSessionState": [{
32684
+ name: "deviceId",
32685
+ form: "single",
32686
+ optional: false
32687
+ }],
32688
+ "webrtcSession.handleAnswer": [{
32689
+ name: "deviceId",
32690
+ form: "single",
32691
+ optional: false
32692
+ }],
32693
+ "webrtcSession.handleOffer": [{
32694
+ name: "deviceId",
32695
+ form: "single",
32696
+ optional: false
32697
+ }],
32698
+ "webrtcSession.hasAdaptiveBitrate": [{
32699
+ name: "deviceId",
32700
+ form: "single",
32701
+ optional: false
32702
+ }],
32703
+ "webrtcSession.listStreams": [{
32704
+ name: "deviceId",
32705
+ form: "single",
32706
+ optional: false
32707
+ }],
32708
+ "zoneAnalytics.getCameraHistory": [{
32709
+ name: "deviceId",
32710
+ form: "single",
32711
+ optional: false
32712
+ }],
32713
+ "zoneAnalytics.getCurrentSnapshot": [{
32714
+ name: "deviceId",
32715
+ form: "single",
32716
+ optional: false
32717
+ }],
32718
+ "zoneAnalytics.getUnzonedHistory": [{
32719
+ name: "deviceId",
32720
+ form: "single",
32721
+ optional: false
32722
+ }],
32723
+ "zoneAnalytics.getZoneHistory": [{
32724
+ name: "deviceId",
32725
+ form: "single",
32726
+ optional: false
32727
+ }],
32728
+ "zoneRules.listRules": [{
32729
+ name: "deviceId",
32730
+ form: "single",
32731
+ optional: false
32732
+ }],
32733
+ "zoneRules.setRules": [{
32734
+ name: "deviceId",
32735
+ form: "single",
32736
+ optional: false
32737
+ }],
32738
+ "zones.addZone": [{
32739
+ name: "deviceId",
32740
+ form: "single",
32741
+ optional: false
32742
+ }],
32743
+ "zones.listZones": [{
32744
+ name: "deviceId",
32745
+ form: "single",
32746
+ optional: false
32747
+ }],
32748
+ "zones.removeZone": [{
32749
+ name: "deviceId",
32750
+ form: "single",
32751
+ optional: false
32752
+ }],
32753
+ "zones.updateZone": [{
32754
+ name: "deviceId",
32755
+ form: "single",
32756
+ optional: false
32757
+ }]
32758
+ });
30643
32759
  Object.freeze({
30644
32760
  "broker": "broker",
30645
32761
  "device-export": "device-export",
@@ -31067,7 +33183,7 @@ function mapHttpError(res) {
31067
33183
  };
31068
33184
  }
31069
33185
  /** Narrow an unknown to a plain object record (documented JSON type boundary). */
31070
- function asRecord(value) {
33186
+ function asRecord$1(value) {
31071
33187
  if (value !== null && typeof value === "object" && !Array.isArray(value)) return value;
31072
33188
  return null;
31073
33189
  }
@@ -31194,10 +33310,10 @@ function buildChatCompletionsBody(profile, request) {
31194
33310
  };
31195
33311
  return body;
31196
33312
  }
31197
- function readString$2(value) {
33313
+ function readString$3(value) {
31198
33314
  return typeof value === "string" ? value : void 0;
31199
33315
  }
31200
- function readNumber$2(value) {
33316
+ function readNumber$3(value) {
31201
33317
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
31202
33318
  }
31203
33319
  function parseChatCompletionsResult(res, latencyMs) {
@@ -31209,33 +33325,33 @@ function parseChatCompletionsResult(res, latencyMs) {
31209
33325
  code: "adapter-error",
31210
33326
  message: "invalid JSON response"
31211
33327
  };
31212
- const choice = asRecord((Array.isArray(body.choices) ? body.choices : [])[0]);
33328
+ const choice = asRecord$1((Array.isArray(body.choices) ? body.choices : [])[0]);
31213
33329
  if (choice === null) return {
31214
33330
  ok: false,
31215
33331
  code: "adapter-error",
31216
33332
  message: "missing choices[0]"
31217
33333
  };
31218
- const finishReason = readString$2(choice.finish_reason);
33334
+ const finishReason = readString$3(choice.finish_reason);
31219
33335
  if (finishReason === "content_filter") return {
31220
33336
  ok: false,
31221
33337
  code: "refusal",
31222
33338
  message: "content filtered"
31223
33339
  };
31224
- const messageObj = asRecord(choice.message);
31225
- const text = messageObj !== null ? readString$2(messageObj.content) : void 0;
33340
+ const messageObj = asRecord$1(choice.message);
33341
+ const text = messageObj !== null ? readString$3(messageObj.content) : void 0;
31226
33342
  if (text === void 0) return {
31227
33343
  ok: false,
31228
33344
  code: "adapter-error",
31229
33345
  message: "missing message.content"
31230
33346
  };
31231
- const usageObj = asRecord(body.usage) ?? {};
33347
+ const usageObj = asRecord$1(body.usage) ?? {};
31232
33348
  return {
31233
33349
  ok: true,
31234
33350
  text,
31235
- model: readString$2(body.model) ?? "",
33351
+ model: readString$3(body.model) ?? "",
31236
33352
  usage: {
31237
- inputTokens: readNumber$2(usageObj.prompt_tokens),
31238
- outputTokens: readNumber$2(usageObj.completion_tokens)
33353
+ inputTokens: readNumber$3(usageObj.prompt_tokens),
33354
+ outputTokens: readNumber$3(usageObj.completion_tokens)
31239
33355
  },
31240
33356
  truncated: finishReason === "length",
31241
33357
  latencyMs
@@ -31246,7 +33362,7 @@ function parseModelsList(res) {
31246
33362
  const data = body !== null && Array.isArray(body.data) ? body.data : [];
31247
33363
  const ids = [];
31248
33364
  for (const entry of data) if (entry !== null && typeof entry === "object") {
31249
- const id = readString$2(entry.id);
33365
+ const id = readString$3(entry.id);
31250
33366
  if (id !== void 0) ids.push(id);
31251
33367
  }
31252
33368
  return ids;
@@ -31312,10 +33428,10 @@ function userContent(request) {
31312
33428
  });
31313
33429
  return parts;
31314
33430
  }
31315
- function readString$1(value) {
33431
+ function readString$2(value) {
31316
33432
  return typeof value === "string" ? value : void 0;
31317
33433
  }
31318
- function readNumber$1(value) {
33434
+ function readNumber$2(value) {
31319
33435
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
31320
33436
  }
31321
33437
  function buildMessagesBody(profile, request) {
@@ -31352,7 +33468,7 @@ function parseMessagesResult(res, latencyMs) {
31352
33468
  code: "adapter-error",
31353
33469
  message: "invalid JSON response"
31354
33470
  };
31355
- const stopReason = readString$1(body.stop_reason);
33471
+ const stopReason = readString$2(body.stop_reason);
31356
33472
  if (stopReason === "refusal") return {
31357
33473
  ok: false,
31358
33474
  code: "refusal",
@@ -31367,7 +33483,7 @@ function parseMessagesResult(res, latencyMs) {
31367
33483
  text = JSON.stringify(b.input);
31368
33484
  break;
31369
33485
  }
31370
- if (b.type === "text" && text === void 0) text = readString$1(b.text);
33486
+ if (b.type === "text" && text === void 0) text = readString$2(b.text);
31371
33487
  }
31372
33488
  if (text === void 0) return {
31373
33489
  ok: false,
@@ -31378,10 +33494,10 @@ function parseMessagesResult(res, latencyMs) {
31378
33494
  return {
31379
33495
  ok: true,
31380
33496
  text,
31381
- model: readString$1(body.model) ?? "",
33497
+ model: readString$2(body.model) ?? "",
31382
33498
  usage: {
31383
- inputTokens: readNumber$1(usageObj.input_tokens),
31384
- outputTokens: readNumber$1(usageObj.output_tokens)
33499
+ inputTokens: readNumber$2(usageObj.input_tokens),
33500
+ outputTokens: readNumber$2(usageObj.output_tokens)
31385
33501
  },
31386
33502
  truncated: stopReason === "max_tokens",
31387
33503
  latencyMs
@@ -31416,7 +33532,7 @@ var anthropicAdapter = {
31416
33532
  const data = body !== null && Array.isArray(body.data) ? body.data : [];
31417
33533
  const ids = [];
31418
33534
  for (const entry of data) if (entry !== null && typeof entry === "object") {
31419
- const id = readString$1(entry.id);
33535
+ const id = readString$2(entry.id);
31420
33536
  if (id !== void 0) ids.push(id);
31421
33537
  }
31422
33538
  return ids;
@@ -31434,10 +33550,10 @@ function headers(profile) {
31434
33550
  ...profile.extraHeaders
31435
33551
  };
31436
33552
  }
31437
- function readString(value) {
33553
+ function readString$1(value) {
31438
33554
  return typeof value === "string" ? value : void 0;
31439
33555
  }
31440
- function readNumber(value) {
33556
+ function readNumber$1(value) {
31441
33557
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
31442
33558
  }
31443
33559
  function buildGenerateContentBody(profile, request) {
@@ -31472,37 +33588,37 @@ function parseGenerateContentResult(res, latencyMs) {
31472
33588
  code: "adapter-error",
31473
33589
  message: "invalid JSON response"
31474
33590
  };
31475
- const promptFeedback = asRecord(body.promptFeedback);
31476
- if (promptFeedback !== null && readString(promptFeedback.blockReason) !== void 0) return {
33591
+ const promptFeedback = asRecord$1(body.promptFeedback);
33592
+ if (promptFeedback !== null && readString$1(promptFeedback.blockReason) !== void 0) return {
31477
33593
  ok: false,
31478
33594
  code: "refusal",
31479
- message: `blocked: ${readString(promptFeedback.blockReason)}`
33595
+ message: `blocked: ${readString$1(promptFeedback.blockReason)}`
31480
33596
  };
31481
- const candidate = asRecord((Array.isArray(body.candidates) ? body.candidates : [])[0]);
33597
+ const candidate = asRecord$1((Array.isArray(body.candidates) ? body.candidates : [])[0]);
31482
33598
  if (candidate === null) return {
31483
33599
  ok: false,
31484
33600
  code: "adapter-error",
31485
33601
  message: "missing candidates[0]"
31486
33602
  };
31487
- const finishReason = readString(candidate.finishReason);
33603
+ const finishReason = readString$1(candidate.finishReason);
31488
33604
  if (finishReason === "SAFETY" || finishReason === "PROHIBITED_CONTENT") return {
31489
33605
  ok: false,
31490
33606
  code: "refusal",
31491
33607
  message: `finish: ${finishReason}`
31492
33608
  };
31493
- const contentObj = asRecord(candidate.content) ?? {};
33609
+ const contentObj = asRecord$1(candidate.content) ?? {};
31494
33610
  const text = (Array.isArray(contentObj.parts) ? contentObj.parts : []).map((p) => {
31495
- const rec = asRecord(p);
31496
- return rec !== null ? readString(rec.text) : void 0;
33611
+ const rec = asRecord$1(p);
33612
+ return rec !== null ? readString$1(rec.text) : void 0;
31497
33613
  }).filter((t) => t !== void 0).join("");
31498
- const usageObj = asRecord(body.usageMetadata) ?? {};
33614
+ const usageObj = asRecord$1(body.usageMetadata) ?? {};
31499
33615
  return {
31500
33616
  ok: true,
31501
33617
  text,
31502
- model: readString(body.modelVersion) ?? "",
33618
+ model: readString$1(body.modelVersion) ?? "",
31503
33619
  usage: {
31504
- inputTokens: readNumber(usageObj.promptTokenCount),
31505
- outputTokens: readNumber(usageObj.candidatesTokenCount)
33620
+ inputTokens: readNumber$1(usageObj.promptTokenCount),
33621
+ outputTokens: readNumber$1(usageObj.candidatesTokenCount)
31506
33622
  },
31507
33623
  truncated: finishReason === "MAX_TOKENS",
31508
33624
  latencyMs
@@ -31540,7 +33656,7 @@ var googleAdapter = {
31540
33656
  const models = body !== null && Array.isArray(body.models) ? body.models : [];
31541
33657
  const ids = [];
31542
33658
  for (const entry of models) if (entry !== null && typeof entry === "object") {
31543
- const name = readString(entry.name);
33659
+ const name = readString$1(entry.name);
31544
33660
  if (name !== void 0) ids.push(stripModelsPrefix(name));
31545
33661
  }
31546
33662
  return ids;
@@ -32713,10 +34829,843 @@ async function assembleAi(deps) {
32713
34829
  runtimeProvider,
32714
34830
  llmProvider,
32715
34831
  store,
34832
+ usage,
32716
34833
  prune: (retentionDays) => usage.prune(retentionDays)
32717
34834
  };
32718
34835
  }
32719
34836
  //#endregion
34837
+ //#region src/test-chat/chat-completions-stream.ts
34838
+ /** The profile kinds whose wire this module speaks. */
34839
+ var STREAMABLE_KINDS = new Set(["openai-compatible", "openai"]);
34840
+ var OPENAI_DEFAULT_BASE_URL = "https://api.openai.com/v1";
34841
+ function baseUrlFor(profile) {
34842
+ if (profile.baseUrl !== void 0 && profile.baseUrl.length > 0) return trimTrailingSlash(profile.baseUrl);
34843
+ return profile.kind === "openai" ? OPENAI_DEFAULT_BASE_URL : null;
34844
+ }
34845
+ function readString(value) {
34846
+ return typeof value === "string" ? value : void 0;
34847
+ }
34848
+ function readNumber(value) {
34849
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
34850
+ }
34851
+ function asRecord(value) {
34852
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
34853
+ }
34854
+ /**
34855
+ * Split a byte stream into SSE `data:` payloads.
34856
+ *
34857
+ * Chunk boundaries fall wherever TCP puts them, so a frame is buffered until
34858
+ * its newline arrives — reading a half-written JSON object is the classic way
34859
+ * a streaming client "works" until the first slow link.
34860
+ */
34861
+ async function* sseData(stream) {
34862
+ const decoder = new TextDecoder();
34863
+ let buffer = "";
34864
+ for await (const chunk of stream) {
34865
+ buffer += decoder.decode(chunk, { stream: true });
34866
+ let index = buffer.indexOf("\n");
34867
+ while (index >= 0) {
34868
+ const line = buffer.slice(0, index).replace(/\r$/, "");
34869
+ buffer = buffer.slice(index + 1);
34870
+ if (line.startsWith("data:")) yield line.slice(5).trim();
34871
+ index = buffer.indexOf("\n");
34872
+ }
34873
+ }
34874
+ const tail = buffer.replace(/\r$/, "");
34875
+ if (tail.startsWith("data:")) yield tail.slice(5).trim();
34876
+ }
34877
+ /** SSE payloads → normalised chunks. Ends at `[DONE]` or stream end. */
34878
+ async function* chunksFromSse(payloads) {
34879
+ let inputTokens = 0;
34880
+ let outputTokens = 0;
34881
+ let truncated = false;
34882
+ for await (const payload of payloads) {
34883
+ if (payload === "") continue;
34884
+ if (payload === "[DONE]") break;
34885
+ let parsed;
34886
+ try {
34887
+ parsed = JSON.parse(payload);
34888
+ } catch {
34889
+ continue;
34890
+ }
34891
+ const body = asRecord(parsed);
34892
+ if (body === null) continue;
34893
+ const usage = asRecord(body.usage);
34894
+ if (usage !== null) {
34895
+ inputTokens = readNumber(usage.prompt_tokens);
34896
+ outputTokens = readNumber(usage.completion_tokens);
34897
+ }
34898
+ const choice = asRecord((Array.isArray(body.choices) ? body.choices : [])[0]);
34899
+ if (choice === null) continue;
34900
+ if (readString(choice.finish_reason) === "length") truncated = true;
34901
+ const delta = asRecord(choice.delta);
34902
+ const text = delta !== null ? readString(delta.content) : void 0;
34903
+ if (text !== void 0 && text.length > 0) yield {
34904
+ kind: "token",
34905
+ text
34906
+ };
34907
+ }
34908
+ yield {
34909
+ kind: "end",
34910
+ inputTokens,
34911
+ outputTokens,
34912
+ truncated
34913
+ };
34914
+ }
34915
+ /**
34916
+ * Open the streamed completion.
34917
+ *
34918
+ * The connect bound covers ONLY the wait for response headers — once the
34919
+ * provider has answered, the first-token and idle bounds take over upstream.
34920
+ * They are separated because "the port is closed" and "the model is loading"
34921
+ * deserve different patience and different sentences.
34922
+ */
34923
+ function createChatCompletionsStreamer(deps = {}) {
34924
+ const fetchImpl = deps.fetchImpl ?? fetch;
34925
+ return async (request, opts) => {
34926
+ const profile = request.profile;
34927
+ if (!STREAMABLE_KINDS.has(profile.kind)) return { kind: "unsupported" };
34928
+ const base = baseUrlFor(profile);
34929
+ if (base === null) return { kind: "unsupported" };
34930
+ const body = {
34931
+ ...buildChatCompletionsBody(profile, {
34932
+ prompt: request.prompt,
34933
+ ...request.system !== void 0 ? { system: request.system } : {},
34934
+ ...request.images !== void 0 ? { images: request.images } : {},
34935
+ ...request.maxTokens !== void 0 ? { maxTokens: request.maxTokens } : {},
34936
+ ...request.temperature !== void 0 ? { temperature: request.temperature } : {}
34937
+ }),
34938
+ stream: true,
34939
+ stream_options: { include_usage: true }
34940
+ };
34941
+ const connectController = new AbortController();
34942
+ const onOuterAbort = () => connectController.abort();
34943
+ opts.signal.addEventListener("abort", onOuterAbort, { once: true });
34944
+ const connectTimer = setTimeout(() => connectController.abort(), opts.connectTimeoutMs);
34945
+ connectTimer.unref?.();
34946
+ let res;
34947
+ try {
34948
+ res = await fetchImpl(`${base}/chat/completions`, {
34949
+ method: "POST",
34950
+ headers: {
34951
+ ...authHeaders(profile),
34952
+ accept: "text/event-stream"
34953
+ },
34954
+ body: JSON.stringify(body),
34955
+ signal: connectController.signal
34956
+ });
34957
+ } catch (cause) {
34958
+ if (cause instanceof Error && cause.name === "AbortError" && !opts.signal.aborted) return {
34959
+ kind: "connect-timeout",
34960
+ timeoutMs: opts.connectTimeoutMs
34961
+ };
34962
+ return {
34963
+ kind: "network",
34964
+ message: cause instanceof Error ? cause.message : String(cause)
34965
+ };
34966
+ } finally {
34967
+ clearTimeout(connectTimer);
34968
+ opts.signal.removeEventListener("abort", onOuterAbort);
34969
+ }
34970
+ if (!res.ok) {
34971
+ const detail = (await res.text().catch(() => "")).slice(0, 500);
34972
+ return {
34973
+ kind: "network",
34974
+ message: `HTTP ${String(res.status)}${detail ? `: ${detail}` : ""}`
34975
+ };
34976
+ }
34977
+ if (res.body === null) return {
34978
+ kind: "network",
34979
+ message: "the provider returned no body"
34980
+ };
34981
+ const bytes = res.body;
34982
+ return {
34983
+ kind: "open",
34984
+ chunks: chunksFromSse(sseData(bytes))
34985
+ };
34986
+ };
34987
+ }
34988
+ /** Per-message cap — the prompt is flattened, so this bounds the whole call. */
34989
+ var TEST_CHAT_MAX_CONTENT_CHARS = 8e3;
34990
+ /** The data-plane prefix under `/addon/ai/`. */
34991
+ var TEST_CHAT_PREFIX = "test-chat";
34992
+ /** The usage tag every test turn is billed under (`llm.getUsage`). */
34993
+ var TEST_CHAT_CONSUMER = "adhoc-ui";
34994
+ var TEST_CHAT_CONNECT_TIMEOUT_MS = 1e4;
34995
+ var TEST_CHAT_DEFAULT_FIRST_TOKEN_TIMEOUT_MS = 12e4;
34996
+ var TEST_CHAT_MAX_FIRST_TOKEN_TIMEOUT_MS = 6e5;
34997
+ var TEST_CHAT_IDLE_TIMEOUT_MS = 6e4;
34998
+ var TestChatMessageSchema = object({
34999
+ role: _enum(["user", "assistant"]),
35000
+ content: string().max(TEST_CHAT_MAX_CONTENT_CHARS)
35001
+ });
35002
+ /**
35003
+ * WHAT to look at, never the pixels themselves.
35004
+ *
35005
+ * `snapshot` is the camera's current frame through the snapshot wrapper (cache
35006
+ * + coalescing + the battery-camera sleep gate all still apply — this is a test
35007
+ * surface and has no business setting `force`).
35008
+ *
35009
+ * `track` is a stored best-shot: the addon picks ONE media row the pipeline
35010
+ * already wrote (see `TRACK_MEDIA_PREFERENCE`) and sends it verbatim. No crop
35011
+ * rectangle is computed here — D52 keeps that in the runner, and a test chat
35012
+ * that derived its own would be judging a rectangle nothing else ever sees.
35013
+ */
35014
+ var TestChatImageRefSchema = discriminatedUnion("kind", [object({
35015
+ kind: literal("snapshot"),
35016
+ deviceId: number().int().positive()
35017
+ }), object({
35018
+ kind: literal("track"),
35019
+ trackId: string().min(1)
35020
+ })]);
35021
+ /**
35022
+ * `.strict()`, and it is load-bearing.
35023
+ *
35024
+ * Measured on the live hub: a request that named the attachment field
35025
+ * `attachment` against a schema that called it `imageRef` was accepted, the
35026
+ * unknown key was STRIPPED by the default object parse, and the run answered a
35027
+ * vision question with a text-only turn — "specify what you want described",
35028
+ * `inputTokens: 10`. No error, no log, no way to tell it from a model that
35029
+ * simply could not see. A stripped field is a silent behaviour change, which is
35030
+ * the one thing a diagnostic surface must never do; `.strict()` turns it into a
35031
+ * 400 naming the key.
35032
+ */
35033
+ var TestChatRequestSchema = object({
35034
+ /** Explicit profile — the test chat NEVER falls through to the default
35035
+ * resolution chain, because "which model answered" is the whole question. */
35036
+ profileId: string().min(1),
35037
+ messages: array(TestChatMessageSchema).min(1).max(40),
35038
+ /** Optional system turn, authored by the operator in the page. */
35039
+ system: string().max(TEST_CHAT_MAX_CONTENT_CHARS).optional(),
35040
+ /** The picture to look at, BY REFERENCE. Named `attachment` because that is
35041
+ * what every caller reaches for; the previous `imageRef` cost a live
35042
+ * debugging session to the strip described above. */
35043
+ attachment: TestChatImageRefSchema.optional(),
35044
+ temperature: number().min(0).max(2).optional(),
35045
+ maxTokens: number().int().positive().max(8192).optional(),
35046
+ /** See the timeout block above. Applies to a VISION turn identically —
35047
+ * vision inference is the slower one, so a bound tuned on text is a bound
35048
+ * that only fails on the interesting case. */
35049
+ firstTokenTimeoutMs: number().int().positive().max(TEST_CHAT_MAX_FIRST_TOKEN_TIMEOUT_MS).default(TEST_CHAT_DEFAULT_FIRST_TOKEN_TIMEOUT_MS)
35050
+ }).strict();
35051
+ /**
35052
+ * Below this, a turn that CARRIED an image almost certainly did not deliver it.
35053
+ *
35054
+ * A vision model prices a picture in hundreds of input tokens (qwen3-vl bills a
35055
+ * 480 px crop at several hundred); a text-only turn of a short prompt bills
35056
+ * about ten. The live failure above reported exactly `10`. So when the provider
35057
+ * tells us the input count and an image was attached, a count this low is
35058
+ * evidence the picture did not reach the model — worth a warning line even
35059
+ * though the run "succeeded", because the answer will look like an answer.
35060
+ *
35061
+ * Only checked when the provider actually reported a count: a streamed
35062
+ * completion without `stream_options.include_usage` reports none, and 0 must
35063
+ * never be read as 10.
35064
+ */
35065
+ var TEST_CHAT_MIN_VISION_INPUT_TOKENS = 100;
35066
+ /**
35067
+ * What the server actually attached — metadata only, never the bytes.
35068
+ *
35069
+ * The operator has to be able to tell a vision answer about the wrong picture
35070
+ * from a vision answer about the right one, and the only way to do that from
35071
+ * the client is to be told which media row was used and how old it is.
35072
+ */
35073
+ var TestChatAttachmentSchema = object({
35074
+ kind: _enum(["snapshot", "track"]),
35075
+ mimeType: string(),
35076
+ sizeBytes: number().int().nonnegative(),
35077
+ /** Track attachments only: the `MediaFileKind` that won `TRACK_MEDIA_PREFERENCE`. */
35078
+ mediaKind: string().optional(),
35079
+ /** Epoch ms of the frame, when the source knows it. */
35080
+ capturedAt: number().optional()
35081
+ });
35082
+ /**
35083
+ * What the run is doing right now — so a wait reads as progress, not as a hang.
35084
+ *
35085
+ * `first-token-wait` is the one that matters: it is the phase a cold model
35086
+ * spends minutes in, and the page renders it as "the model is loading…" rather
35087
+ * than as silence.
35088
+ */
35089
+ var TestChatPhaseSchema = _enum([
35090
+ "resolving-image",
35091
+ "connecting",
35092
+ "first-token-wait",
35093
+ "streaming"
35094
+ ]);
35095
+ /**
35096
+ * One NDJSON line. A discriminated union so the client narrows on `kind` and a
35097
+ * line this build does not understand is skipped rather than half-read.
35098
+ *
35099
+ * `meta` arrives BEFORE the first token: it is what the server committed to —
35100
+ * which profile, which model, which picture — and the page shows it while the
35101
+ * model is still loading, so a long wait is at least an informed one.
35102
+ */
35103
+ var TestChatEventSchema = discriminatedUnion("kind", [
35104
+ object({
35105
+ kind: literal("status"),
35106
+ phase: TestChatPhaseSchema
35107
+ }),
35108
+ object({
35109
+ kind: literal("meta"),
35110
+ profileName: string(),
35111
+ model: string(),
35112
+ /**
35113
+ * WHAT MEDIA WAS ACTUALLY SENT — and the contract is that this is non-null
35114
+ * whenever the request carried an `attachment`.
35115
+ *
35116
+ * There is no path where a requested picture goes missing and the run
35117
+ * continues: a resolution failure ends the stream with an `error` event.
35118
+ * That rule exists because the opposite shipped — a vision question answered
35119
+ * text-only, indistinguishable from a model that looked and saw nothing.
35120
+ */
35121
+ mediaSent: TestChatAttachmentSchema.nullable(),
35122
+ /** False when the profile's wire has no token stream and the whole answer
35123
+ * arrives at once. Said out loud rather than faked. */
35124
+ streamed: boolean(),
35125
+ /** Echoed so the page states the bound it is really working under. */
35126
+ firstTokenTimeoutMs: number()
35127
+ }),
35128
+ object({
35129
+ kind: literal("token"),
35130
+ text: string()
35131
+ }),
35132
+ object({
35133
+ kind: literal("done"),
35134
+ inputTokens: number(),
35135
+ outputTokens: number(),
35136
+ latencyMs: number(),
35137
+ truncated: boolean()
35138
+ }),
35139
+ object({
35140
+ kind: literal("error"),
35141
+ code: string(),
35142
+ message: string()
35143
+ })
35144
+ ]);
35145
+ /** Serialise one event as an NDJSON line, terminator included. */
35146
+ function encodeEvent(event) {
35147
+ return `${JSON.stringify(event)}\n`;
35148
+ }
35149
+ //#endregion
35150
+ //#region src/test-chat/media.ts
35151
+ /**
35152
+ * Which stored media a track attachment is allowed to be, best first.
35153
+ *
35154
+ * `keyFrameSmall` wins because it is the CLEAN best-shot (no drawn box) at a
35155
+ * size a vision model actually ingests — a boxed frame teaches the model to
35156
+ * describe the annotation, and the native `keyFrame` is megabytes for no gain.
35157
+ * The subject crops come next for a track whose key frame was never written,
35158
+ * and `fullFrameBoxed` is absent on purpose: it is the one variant with
35159
+ * graphics painted over the evidence.
35160
+ */
35161
+ var TRACK_MEDIA_PREFERENCE = [
35162
+ "keyFrameSmall",
35163
+ "keyFrame",
35164
+ "thumbnailSmall",
35165
+ "thumbnail",
35166
+ "crop"
35167
+ ];
35168
+ /** JPEG is what every media row and every snapshot in this system is. */
35169
+ var DEFAULT_MIME = "image/jpeg";
35170
+ /**
35171
+ * Flatten the transcript into the single prompt the chat-completions wire takes
35172
+ * as one user turn.
35173
+ *
35174
+ * A SINGLE user turn is sent verbatim: prefixing one message with `User:`
35175
+ * teaches small local models to answer in the same transcript format, which
35176
+ * makes the very first test a confusing one.
35177
+ */
35178
+ function renderTranscript(messages) {
35179
+ if (messages.length === 1 && messages[0].role === "user") return messages[0].content;
35180
+ return messages.map((m) => `${m.role === "user" ? "User" : "Assistant"}: ${m.content}`).join("\n\n");
35181
+ }
35182
+ /** The highest-preference media row present, or null when none qualifies. */
35183
+ function pickTrackMedia(rows) {
35184
+ for (const kind of TRACK_MEDIA_PREFERENCE) {
35185
+ const hit = rows.find((r) => r.kind === kind);
35186
+ if (hit !== void 0) return hit;
35187
+ }
35188
+ return null;
35189
+ }
35190
+ function decode(base64) {
35191
+ const buf = Buffer.from(base64, "base64");
35192
+ const bytes = new Uint8Array(new ArrayBuffer(buf.byteLength));
35193
+ bytes.set(buf);
35194
+ return bytes;
35195
+ }
35196
+ async function resolveImage(deps, ref) {
35197
+ if (ref.kind === "snapshot") {
35198
+ const tags = { deviceId: ref.deviceId };
35199
+ let shot;
35200
+ try {
35201
+ shot = await deps.getSnapshot(ref.deviceId);
35202
+ } catch (cause) {
35203
+ const message = cause instanceof Error ? cause.message : String(cause);
35204
+ deps.logger.warn("ai test chat: snapshot read failed — turn dropped", {
35205
+ tags,
35206
+ meta: { error: message }
35207
+ });
35208
+ return {
35209
+ ok: false,
35210
+ failure: {
35211
+ code: "unavailable",
35212
+ message: `snapshot unavailable: ${message}`
35213
+ }
35214
+ };
35215
+ }
35216
+ if (shot === null) {
35217
+ deps.logger.warn("ai test chat: camera has no frame — turn dropped", { tags });
35218
+ return {
35219
+ ok: false,
35220
+ failure: {
35221
+ code: "unavailable",
35222
+ message: "this camera has produced no frame yet"
35223
+ }
35224
+ };
35225
+ }
35226
+ const bytes = decode(shot.base64);
35227
+ const mimeType = shot.contentType.length > 0 ? shot.contentType : DEFAULT_MIME;
35228
+ return {
35229
+ ok: true,
35230
+ resolved: {
35231
+ image: {
35232
+ bytes,
35233
+ mimeType
35234
+ },
35235
+ attachment: {
35236
+ kind: "snapshot",
35237
+ mimeType,
35238
+ sizeBytes: bytes.byteLength
35239
+ }
35240
+ }
35241
+ };
35242
+ }
35243
+ let rows;
35244
+ try {
35245
+ rows = await deps.getTrackMedia(ref.trackId, TRACK_MEDIA_PREFERENCE);
35246
+ } catch (cause) {
35247
+ const message = cause instanceof Error ? cause.message : String(cause);
35248
+ deps.logger.warn("ai test chat: track media read failed — turn dropped", { meta: {
35249
+ trackId: ref.trackId,
35250
+ error: message
35251
+ } });
35252
+ return {
35253
+ ok: false,
35254
+ failure: {
35255
+ code: "unavailable",
35256
+ message: `track media unavailable: ${message}`
35257
+ }
35258
+ };
35259
+ }
35260
+ const row = pickTrackMedia(rows);
35261
+ if (row === null) {
35262
+ deps.logger.warn("ai test chat: track has no usable media — turn dropped", { meta: {
35263
+ trackId: ref.trackId,
35264
+ kindsFound: rows.map((r) => r.kind)
35265
+ } });
35266
+ return {
35267
+ ok: false,
35268
+ failure: {
35269
+ code: "unavailable",
35270
+ message: "this track has no stored best-shot left (evicted or never written)"
35271
+ }
35272
+ };
35273
+ }
35274
+ const bytes = decode(row.base64);
35275
+ return {
35276
+ ok: true,
35277
+ resolved: {
35278
+ image: {
35279
+ bytes,
35280
+ mimeType: DEFAULT_MIME
35281
+ },
35282
+ attachment: {
35283
+ kind: "track",
35284
+ mimeType: DEFAULT_MIME,
35285
+ sizeBytes: bytes.byteLength,
35286
+ mediaKind: row.kind,
35287
+ capturedAt: row.timestamp
35288
+ }
35289
+ }
35290
+ };
35291
+ }
35292
+ //#endregion
35293
+ //#region src/test-chat/run-stream.ts
35294
+ /** Race `promise` against a timer; `null` means the timer won. */
35295
+ async function withDeadline(promise, ms) {
35296
+ let timer;
35297
+ const expiry = new Promise((resolve) => {
35298
+ timer = setTimeout(() => resolve({ timedOut: true }), ms);
35299
+ timer.unref?.();
35300
+ });
35301
+ try {
35302
+ return await Promise.race([promise.then((value) => ({
35303
+ timedOut: false,
35304
+ value
35305
+ })), expiry]);
35306
+ } finally {
35307
+ if (timer !== void 0) clearTimeout(timer);
35308
+ }
35309
+ }
35310
+ /**
35311
+ * Pull the next chunk under a deadline.
35312
+ *
35313
+ * The iterator is NOT abandoned on a timeout — the caller stops reading and the
35314
+ * signal tears the socket down, which is what actually frees the provider. A
35315
+ * dangling `next()` promise here would keep the generator alive.
35316
+ */
35317
+ async function nextChunk(iterator, ms) {
35318
+ const raced = await withDeadline(iterator.next(), ms);
35319
+ if (raced.timedOut) return { kind: "timeout" };
35320
+ return raced.value.done === true ? { kind: "end" } : {
35321
+ kind: "chunk",
35322
+ value: raced.value.value
35323
+ };
35324
+ }
35325
+ async function runTestChatStream(deps, request, emit, signal) {
35326
+ const startedAt = deps.now();
35327
+ const tags = request.attachment?.kind === "snapshot" ? { deviceId: request.attachment.deviceId } : void 0;
35328
+ const withTags = (meta) => tags !== void 0 ? {
35329
+ tags,
35330
+ meta
35331
+ } : { meta };
35332
+ let profileForUsage = null;
35333
+ let imageCount = 0;
35334
+ let finished = false;
35335
+ const finish = async (ending, counts) => {
35336
+ if (finished) return;
35337
+ finished = true;
35338
+ const latencyMs = deps.now() - startedAt;
35339
+ await deps.recordUsage({
35340
+ ts: startedAt,
35341
+ consumer: TEST_CHAT_CONSUMER,
35342
+ ...profileForUsage !== null ? {
35343
+ profileId: profileForUsage.id,
35344
+ kind: profileForUsage.kind,
35345
+ model: profileForUsage.model
35346
+ } : {},
35347
+ inputTokens: counts.inputTokens,
35348
+ outputTokens: counts.outputTokens,
35349
+ latencyMs,
35350
+ ok: ending === null,
35351
+ ...ending !== null ? { errorCode: ending.code } : {},
35352
+ imageCount
35353
+ }).catch(() => void 0);
35354
+ if (ending !== null) emit({
35355
+ kind: "error",
35356
+ code: ending.code,
35357
+ message: ending.message
35358
+ });
35359
+ };
35360
+ const fail = async (ending, logMessage, meta = {}) => {
35361
+ deps.logger.warn(logMessage, withTags({
35362
+ profileId: request.profileId,
35363
+ ...meta
35364
+ }));
35365
+ await finish(ending, {
35366
+ inputTokens: 0,
35367
+ outputTokens: 0
35368
+ });
35369
+ };
35370
+ if (signal.aborted) {
35371
+ deps.logger.info("ai test chat: client had already left before the run started", withTags({}));
35372
+ return;
35373
+ }
35374
+ const profile = await deps.getProfile(request.profileId);
35375
+ if (profile === null || !profile.enabled) {
35376
+ await fail({
35377
+ code: "no-profile",
35378
+ message: `profile "${request.profileId}" not found or disabled`
35379
+ }, "ai test chat: profile missing or disabled — turn dropped");
35380
+ return;
35381
+ }
35382
+ profileForUsage = profile;
35383
+ let attachment = null;
35384
+ let images;
35385
+ if (request.attachment !== void 0) {
35386
+ if (!profile.supportsVision) {
35387
+ await fail({
35388
+ code: "bad-request",
35389
+ message: `profile "${profile.name}" is not vision-capable`
35390
+ }, "ai test chat: profile is not vision-capable — image turn refused", { model: profile.model });
35391
+ return;
35392
+ }
35393
+ emit({
35394
+ kind: "status",
35395
+ phase: "resolving-image"
35396
+ });
35397
+ const outcome = await resolveImage(deps, request.attachment);
35398
+ if (!outcome.ok) {
35399
+ await fail(outcome.failure, "ai test chat: attachment could not be resolved — vision turn refused", { attachmentKind: request.attachment.kind });
35400
+ return;
35401
+ }
35402
+ attachment = outcome.resolved.attachment;
35403
+ images = [outcome.resolved.image];
35404
+ imageCount = 1;
35405
+ }
35406
+ if (signal.aborted) {
35407
+ deps.logger.info("ai test chat: client aborted before the provider was called", withTags({}));
35408
+ return;
35409
+ }
35410
+ const streamRequest = {
35411
+ profile,
35412
+ prompt: renderTranscript(request.messages),
35413
+ ...request.system !== void 0 ? { system: request.system } : {},
35414
+ ...images !== void 0 ? { images } : {},
35415
+ ...request.maxTokens !== void 0 ? { maxTokens: request.maxTokens } : {},
35416
+ ...request.temperature !== void 0 ? { temperature: request.temperature } : {}
35417
+ };
35418
+ emit({
35419
+ kind: "status",
35420
+ phase: "connecting"
35421
+ });
35422
+ const opened = await deps.openStream(streamRequest, {
35423
+ signal,
35424
+ connectTimeoutMs: TEST_CHAT_CONNECT_TIMEOUT_MS
35425
+ });
35426
+ if (opened.kind === "connect-timeout") {
35427
+ await fail({
35428
+ code: "unavailable",
35429
+ message: `the endpoint did not accept the connection within ${String(Math.round(opened.timeoutMs / 1e3))}s — check that the provider is running and the base URL is right`
35430
+ }, "ai test chat: provider endpoint unreachable (connect timeout) — turn dropped", {
35431
+ baseUrl: profile.baseUrl ?? "",
35432
+ connectTimeoutMs: opened.timeoutMs
35433
+ });
35434
+ return;
35435
+ }
35436
+ if (opened.kind === "network") {
35437
+ await fail({
35438
+ code: "unavailable",
35439
+ message: `the endpoint could not be reached: ${opened.message}`
35440
+ }, "ai test chat: provider endpoint unreachable (network error) — turn dropped", {
35441
+ baseUrl: profile.baseUrl ?? "",
35442
+ error: opened.message
35443
+ });
35444
+ return;
35445
+ }
35446
+ const streamed = opened.kind === "open";
35447
+ emit({
35448
+ kind: "meta",
35449
+ profileName: profile.name,
35450
+ model: profile.model,
35451
+ mediaSent: attachment,
35452
+ streamed,
35453
+ firstTokenTimeoutMs: request.firstTokenTimeoutMs
35454
+ });
35455
+ const warnIfPictureLikelyMissing = (inputTokens) => {
35456
+ if (attachment === null || inputTokens <= 0) return;
35457
+ if (inputTokens >= 100) return;
35458
+ deps.logger.warn("ai test chat: image turn billed like a text turn — the picture may not have reached the model", withTags({
35459
+ profileId: profile.id,
35460
+ model: profile.model,
35461
+ inputTokens,
35462
+ expectedAtLeast: 100,
35463
+ attachmentBytes: attachment.sizeBytes
35464
+ }));
35465
+ };
35466
+ if (!streamed) {
35467
+ emit({
35468
+ kind: "status",
35469
+ phase: "first-token-wait"
35470
+ });
35471
+ const raced = await withDeadline(deps.generateOnce(streamRequest), request.firstTokenTimeoutMs);
35472
+ if (signal.aborted) {
35473
+ deps.logger.info("ai test chat: client aborted mid-generation", withTags({}));
35474
+ return;
35475
+ }
35476
+ if (raced.timedOut) {
35477
+ await failFirstToken();
35478
+ return;
35479
+ }
35480
+ const result = raced.value;
35481
+ if (!result.ok) {
35482
+ await fail({
35483
+ code: result.code,
35484
+ message: result.message
35485
+ }, "ai test chat: provider returned an error — turn dropped", {
35486
+ code: result.code,
35487
+ error: result.message
35488
+ });
35489
+ return;
35490
+ }
35491
+ warnIfPictureLikelyMissing(result.usage.inputTokens);
35492
+ emit({
35493
+ kind: "token",
35494
+ text: result.text
35495
+ });
35496
+ emit({
35497
+ kind: "done",
35498
+ inputTokens: result.usage.inputTokens,
35499
+ outputTokens: result.usage.outputTokens,
35500
+ latencyMs: deps.now() - startedAt,
35501
+ truncated: result.truncated
35502
+ });
35503
+ await finish(null, {
35504
+ inputTokens: result.usage.inputTokens,
35505
+ outputTokens: result.usage.outputTokens
35506
+ });
35507
+ return;
35508
+ }
35509
+ async function failFirstToken() {
35510
+ await fail({
35511
+ code: "timeout",
35512
+ message: `the model produced no output within ${String(Math.round(request.firstTokenTimeoutMs / 1e3))}s — a cold load can take minutes; warm the model or raise the first-token timeout`
35513
+ }, "ai test chat: no first token before the bound — model probably still loading", { firstTokenTimeoutMs: request.firstTokenTimeoutMs });
35514
+ }
35515
+ emit({
35516
+ kind: "status",
35517
+ phase: "first-token-wait"
35518
+ });
35519
+ const iterator = opened.chunks[Symbol.asyncIterator]();
35520
+ let sawFirstToken = false;
35521
+ let inputTokens = 0;
35522
+ let outputTokens = 0;
35523
+ let truncated = false;
35524
+ for (;;) {
35525
+ if (signal.aborted) {
35526
+ deps.logger.info("ai test chat: client aborted mid-stream — provider call torn down", { ...withTags({ sawFirstToken }) });
35527
+ return;
35528
+ }
35529
+ const next = await nextChunk(iterator, sawFirstToken ? TEST_CHAT_IDLE_TIMEOUT_MS : request.firstTokenTimeoutMs);
35530
+ if (next.kind === "timeout") {
35531
+ if (!sawFirstToken) {
35532
+ await failFirstToken();
35533
+ return;
35534
+ }
35535
+ await fail({
35536
+ code: "timeout",
35537
+ message: `the answer stopped mid-stream — no further output for ${String(Math.round(TEST_CHAT_IDLE_TIMEOUT_MS / 1e3))}s`
35538
+ }, "ai test chat: stream stalled after the first token — turn cut short", {
35539
+ idleTimeoutMs: TEST_CHAT_IDLE_TIMEOUT_MS,
35540
+ outputTokens
35541
+ });
35542
+ return;
35543
+ }
35544
+ if (next.kind === "end") break;
35545
+ if (next.value.kind === "token") {
35546
+ if (!sawFirstToken) {
35547
+ sawFirstToken = true;
35548
+ emit({
35549
+ kind: "status",
35550
+ phase: "streaming"
35551
+ });
35552
+ }
35553
+ emit({
35554
+ kind: "token",
35555
+ text: next.value.text
35556
+ });
35557
+ continue;
35558
+ }
35559
+ inputTokens = next.value.inputTokens;
35560
+ outputTokens = next.value.outputTokens;
35561
+ truncated = next.value.truncated;
35562
+ }
35563
+ if (signal.aborted) {
35564
+ deps.logger.info("ai test chat: client aborted before the stream finished", withTags({}));
35565
+ return;
35566
+ }
35567
+ if (!sawFirstToken) {
35568
+ await fail({
35569
+ code: "adapter-error",
35570
+ message: "the provider closed the stream without producing any output"
35571
+ }, "ai test chat: provider closed an empty stream — turn dropped", {});
35572
+ return;
35573
+ }
35574
+ warnIfPictureLikelyMissing(inputTokens);
35575
+ emit({
35576
+ kind: "done",
35577
+ inputTokens,
35578
+ outputTokens,
35579
+ latencyMs: deps.now() - startedAt,
35580
+ truncated
35581
+ });
35582
+ await finish(null, {
35583
+ inputTokens,
35584
+ outputTokens
35585
+ });
35586
+ }
35587
+ //#endregion
35588
+ //#region src/test-chat/plane.ts
35589
+ /** Bounded so a malformed or hostile body cannot buy memory. */
35590
+ var MAX_BODY_BYTES = 256 * 1024;
35591
+ async function readBody(req) {
35592
+ const chunks = [];
35593
+ let total = 0;
35594
+ for await (const chunk of req) {
35595
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
35596
+ total += buf.byteLength;
35597
+ if (total > MAX_BODY_BYTES) return null;
35598
+ chunks.push(buf);
35599
+ }
35600
+ return Buffer.concat(chunks).toString("utf8");
35601
+ }
35602
+ function sendJson(res, status, body) {
35603
+ const text = JSON.stringify(body);
35604
+ res.writeHead(status, {
35605
+ "content-type": "application/json",
35606
+ "content-length": Buffer.byteLength(text),
35607
+ "cache-control": "no-store"
35608
+ });
35609
+ res.end(text);
35610
+ }
35611
+ function createTestChatPlaneHandler(deps) {
35612
+ return async (req, res) => {
35613
+ if ((req.method ?? "GET").toUpperCase() !== "POST") {
35614
+ sendJson(res, 405, { error: "POST only" });
35615
+ return;
35616
+ }
35617
+ const raw = await readBody(req);
35618
+ if (raw === null) {
35619
+ sendJson(res, 413, { error: "request body too large" });
35620
+ return;
35621
+ }
35622
+ let parsedJson;
35623
+ try {
35624
+ parsedJson = JSON.parse(raw);
35625
+ } catch {
35626
+ sendJson(res, 400, { error: "invalid JSON body" });
35627
+ return;
35628
+ }
35629
+ const parsed = TestChatRequestSchema.safeParse(parsedJson);
35630
+ if (!parsed.success) {
35631
+ const detail = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
35632
+ deps.logger.warn("ai test chat: request rejected — turn never ran", { meta: { detail } });
35633
+ sendJson(res, 400, {
35634
+ error: "invalid request",
35635
+ detail
35636
+ });
35637
+ return;
35638
+ }
35639
+ const controller = new AbortController();
35640
+ res.on("close", () => controller.abort());
35641
+ res.writeHead(200, {
35642
+ "content-type": "application/x-ndjson; charset=utf-8",
35643
+ "cache-control": "no-store, no-transform",
35644
+ "content-encoding": "identity",
35645
+ "x-no-compression": "1",
35646
+ connection: "keep-alive"
35647
+ });
35648
+ res.flushHeaders();
35649
+ const emit = (event) => {
35650
+ if (res.writableEnded) return;
35651
+ res.write(encodeEvent(event));
35652
+ };
35653
+ try {
35654
+ await runTestChatStream(deps.stream, parsed.data, emit, controller.signal);
35655
+ } catch (cause) {
35656
+ const message = cause instanceof Error ? cause.message : String(cause);
35657
+ deps.logger.error("ai test chat: run threw — stream closed with an error event", { meta: { error: message } });
35658
+ emit({
35659
+ kind: "error",
35660
+ code: "adapter-error",
35661
+ message
35662
+ });
35663
+ } finally {
35664
+ if (!res.writableEnded) res.end();
35665
+ }
35666
+ };
35667
+ }
35668
+ //#endregion
32720
35669
  //#region src/runtime/crash-policy.ts
32721
35670
  var CrashPolicy = class {
32722
35671
  opts;
@@ -33112,6 +36061,8 @@ var USAGE_RETENTION_DAYS = 90;
33112
36061
  var AiAddon = class extends BaseAddon {
33113
36062
  supervisor;
33114
36063
  timers = [];
36064
+ /** Hub only — see `serveTestChatPlane`. */
36065
+ testChatPlane = null;
33115
36066
  constructor() {
33116
36067
  super({});
33117
36068
  }
@@ -33140,16 +36091,21 @@ var AiAddon = class extends BaseAddon {
33140
36091
  binDir,
33141
36092
  logger: logger.child("llama-bin")
33142
36093
  }),
33143
- execute: (await Promise.resolve().then(() => require("./execute-plan-gwFcJjeV.js")).then((n) => n.execute_plan_exports)).createFetchExecutor(),
36094
+ execute: (await Promise.resolve().then(() => require("./execute-plan-7uof6Kam.js"))).createFetchExecutor(),
33144
36095
  ...isHub && api !== void 0 ? { runtimeApi: this.asRuntimeApi(api) } : {},
33145
36096
  ...isHub && api !== void 0 ? { distributeModel: this.makeDistributeModel(api) } : {}
33146
36097
  });
33147
- if (isHub) this.schedulePostBoot(assembly);
36098
+ if (isHub) {
36099
+ this.schedulePostBoot(assembly);
36100
+ await this.serveTestChatPlane(assembly, api);
36101
+ }
33148
36102
  return assembly.registrations;
33149
36103
  }
33150
36104
  async onShutdown() {
33151
36105
  for (const timer of this.timers) clearTimeout(timer);
33152
36106
  this.timers.length = 0;
36107
+ await this.testChatPlane?.dispose();
36108
+ this.testChatPlane = null;
33153
36109
  await this.supervisor?.stop();
33154
36110
  }
33155
36111
  asRuntimeApi(api) {
@@ -33168,6 +36124,78 @@ var AiAddon = class extends BaseAddon {
33168
36124
  });
33169
36125
  };
33170
36126
  }
36127
+ /**
36128
+ * `POST /addon/ai/test-chat` — the admin AI page's streaming test chat.
36129
+ *
36130
+ * A DATA-PLANE route, not a cap method and not a custom action. It shipped
36131
+ * first as `addons.custom` and the operator's first real use failed with
36132
+ * `UDS request timed out after 60000ms`: `callAddonOnChild` passes no options
36133
+ * and therefore takes `DEFAULT_UDS_REQUEST_TIMEOUT_MS`, with no per-call
36134
+ * override on that path. A generation that legitimately takes minutes on a
36135
+ * cold GPU load cannot live inside a unary RPC — and the error it produced
36136
+ * named the transport rather than the model or the endpoint.
36137
+ *
36138
+ * HUB ONLY, and that is not a shortcut: the `llm` provider, the profile store
36139
+ * and the api keys exist only here, and the facility binds `127.0.0.1`, which
36140
+ * the hub could not reach on an agent anyway.
36141
+ *
36142
+ * `getProfile` reads the store DIRECTLY rather than through `listProfiles`,
36143
+ * which redacts the api key — the streamed call needs the real one, and it
36144
+ * never leaves this process either way.
36145
+ *
36146
+ * `getSnapshot` deliberately omits `force`. That flag is an OPERATOR signal
36147
+ * that walks past the wrapper's battery-camera sleep gate, and a chat turn is
36148
+ * not a reason to wake a camera: a stale-but-honest frame is the right answer
36149
+ * and the `meta` event reports how old it is.
36150
+ */
36151
+ async serveTestChatPlane(assembly, api) {
36152
+ const store = assembly.store;
36153
+ const provider = assembly.llmProvider;
36154
+ const usage = assembly.usage;
36155
+ if (store === void 0 || provider === void 0 || usage === void 0 || api === void 0) {
36156
+ this.ctx.logger.warn("addon-ai: test-chat plane not served — no llm provider on this node");
36157
+ return;
36158
+ }
36159
+ const logger = this.ctx.logger.child("llm-test-chat");
36160
+ const stream = {
36161
+ getProfile: (profileId) => store.getById(profileId),
36162
+ openStream: createChatCompletionsStreamer(),
36163
+ generateOnce: async (request) => {
36164
+ const base = {
36165
+ profileId: request.profile.id,
36166
+ consumer: TEST_CHAT_CONSUMER,
36167
+ prompt: request.prompt,
36168
+ ...request.system !== void 0 ? { system: request.system } : {},
36169
+ ...request.maxTokens !== void 0 ? { maxTokens: request.maxTokens } : {},
36170
+ ...request.temperature !== void 0 ? { temperature: request.temperature } : {}
36171
+ };
36172
+ return request.images === void 0 || request.images.length === 0 ? provider.generate(base) : provider.generateVision({
36173
+ ...base,
36174
+ images: [...request.images]
36175
+ });
36176
+ },
36177
+ getSnapshot: (deviceId) => api.snapshot.getSnapshot.query({ deviceId }),
36178
+ getTrackMedia: (trackId, kinds) => api.pipelineAnalytics.getTrackMedia.query({
36179
+ trackId,
36180
+ kinds: [...kinds]
36181
+ }),
36182
+ recordUsage: (row) => usage.record(row),
36183
+ logger,
36184
+ now: () => Date.now()
36185
+ };
36186
+ this.testChatPlane = await this.ctx.dataPlane?.serve({
36187
+ prefix: "test-chat",
36188
+ access: "authenticated",
36189
+ handler: createTestChatPlaneHandler({
36190
+ stream,
36191
+ logger
36192
+ })
36193
+ }) ?? null;
36194
+ this.ctx.logger.info("ai test-chat data-plane served", { meta: {
36195
+ served: this.testChatPlane !== null,
36196
+ path: `/addon/${this.ctx.id}/${TEST_CHAT_PREFIX}`
36197
+ } });
36198
+ }
33171
36199
  schedulePostBoot(assembly) {
33172
36200
  const kick = setTimeout(() => {
33173
36201
  assembly.prune?.(USAGE_RETENTION_DAYS).catch(() => void 0);
@@ -33203,35 +36231,41 @@ var AiAddon = class extends BaseAddon {
33203
36231
  exports.AI_ADDON_ID = AI_ADDON_ID;
33204
36232
  exports.AiAddon = AiAddon;
33205
36233
  exports.default = AiAddon;
33206
- exports.DefaultsStore = DefaultsStore;
33207
36234
  exports.LLM_MODEL_CATALOG = LLM_MODEL_CATALOG;
33208
36235
  exports.LlamaSupervisor = LlamaSupervisor;
33209
36236
  exports.LlmErrorCodeSchema = LlmErrorCodeSchema;
33210
36237
  exports.LlmProfileKindSchema = LlmProfileKindSchema;
33211
- exports.PROFILES_COLLECTION = PROFILES_COLLECTION;
33212
- exports.ProfileStore = ProfileStore;
33213
- exports.REDACTED_MARKER = REDACTED_MARKER;
33214
- exports.SEED_PROFILE_ID = SEED_PROFILE_ID;
36238
+ exports.TEST_CHAT_CONNECT_TIMEOUT_MS = TEST_CHAT_CONNECT_TIMEOUT_MS;
36239
+ exports.TEST_CHAT_CONSUMER = TEST_CHAT_CONSUMER;
36240
+ exports.TEST_CHAT_DEFAULT_FIRST_TOKEN_TIMEOUT_MS = TEST_CHAT_DEFAULT_FIRST_TOKEN_TIMEOUT_MS;
36241
+ exports.TEST_CHAT_IDLE_TIMEOUT_MS = TEST_CHAT_IDLE_TIMEOUT_MS;
36242
+ exports.TEST_CHAT_MAX_FIRST_TOKEN_TIMEOUT_MS = TEST_CHAT_MAX_FIRST_TOKEN_TIMEOUT_MS;
36243
+ exports.TEST_CHAT_MIN_VISION_INPUT_TOKENS = TEST_CHAT_MIN_VISION_INPUT_TOKENS;
36244
+ exports.TEST_CHAT_PREFIX = TEST_CHAT_PREFIX;
36245
+ exports.TRACK_MEDIA_PREFERENCE = TRACK_MEDIA_PREFERENCE;
36246
+ exports.TestChatEventSchema = TestChatEventSchema;
36247
+ exports.TestChatRequestSchema = TestChatRequestSchema;
33215
36248
  exports.__exportAll = __exportAll;
33216
36249
  exports.__toESM = __toESM;
33217
- exports.anthropicAdapter = anthropicAdapter;
33218
- exports.assembleAi = assembleAi;
33219
36250
  exports.boolean = boolean;
33220
36251
  exports.catalogById = catalogById;
36252
+ exports.chunksFromSse = chunksFromSse;
33221
36253
  exports.createApiSettingsStorePort = createApiSettingsStorePort;
36254
+ exports.createChatCompletionsStreamer = createChatCompletionsStreamer;
33222
36255
  exports.createDefaultModelOps = createDefaultModelOps;
33223
36256
  exports.createLlmProvider = createLlmProvider;
33224
36257
  exports.createLlmRuntimeProvider = createLlmRuntimeProvider;
33225
36258
  exports.createMemorySettingsStorePort = createMemorySettingsStorePort;
33226
36259
  exports.createRuntimeClient = createRuntimeClient;
36260
+ exports.createTestChatPlaneHandler = createTestChatPlaneHandler;
36261
+ exports.encodeEvent = encodeEvent;
33227
36262
  exports.entryForRef = entryForRef;
33228
36263
  exports.fileSha256 = fileSha256;
33229
- exports.googleAdapter = googleAdapter;
33230
- exports.mergeProfileSecrets = mergeProfileSecrets;
33231
36264
  exports.number = number;
33232
36265
  exports.object = object;
33233
- exports.openAiAdapter = openAiAdapter;
33234
- exports.openAiCompatibleAdapter = openAiCompatibleAdapter;
33235
- exports.redactProfileForRead = redactProfileForRead;
33236
- exports.resolveProfile = resolveProfile;
36266
+ exports.pickTrackMedia = pickTrackMedia;
36267
+ exports.renderTranscript = renderTranscript;
36268
+ exports.resolveImage = resolveImage;
36269
+ exports.runTestChatStream = runTestChatStream;
36270
+ exports.sseData = sseData;
33237
36271
  exports.string = string;