@blueharford/scrypted-spatial-awareness 0.6.27 → 0.6.28

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/out/plugin.zip CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blueharford/scrypted-spatial-awareness",
3
- "version": "0.6.27",
3
+ "version": "0.6.28",
4
4
  "description": "Cross-camera object tracking for Scrypted NVR with spatial awareness",
5
5
  "author": "Joshua Seidel <blueharford>",
6
6
  "license": "Apache-2.0",
@@ -33,8 +33,8 @@ const { systemManager, mediaManager } = sdk;
33
33
  export interface SpatialReasoningConfig {
34
34
  /** Enable LLM-based descriptions */
35
35
  enableLlm: boolean;
36
- /** Specific LLM device ID to use (if not set, auto-discovers) */
37
- llmDeviceId?: string;
36
+ /** Specific LLM device IDs to use (if not set, auto-discovers all) */
37
+ llmDeviceIds?: string[];
38
38
  /** Enable landmark learning/suggestions */
39
39
  enableLandmarkLearning: boolean;
40
40
  /** Minimum confidence for landmark suggestions */
@@ -449,30 +449,38 @@ export class SpatialReasoningEngine {
449
449
  }> = [];
450
450
  private llmIndex: number = 0;
451
451
 
452
- /** Find LLM devices - uses configured device or auto-discovers all for load balancing */
452
+ /** Find LLM devices - uses configured devices or auto-discovers all for load balancing */
453
453
  private async findAllLlmDevices(): Promise<void> {
454
454
  if (this.llmSearched) return;
455
455
  this.llmSearched = true;
456
456
 
457
457
  try {
458
- // If a specific LLM device is configured, use only that one
459
- if (this.config.llmDeviceId) {
460
- const device = systemManager.getDeviceById(this.config.llmDeviceId);
461
- if (device?.interfaces?.includes('ChatCompletion')) {
462
- const providerTypeEnum = this.detectProviderType(device);
463
- this.llmDevices.push({
464
- device: device as unknown as ChatCompletionDevice,
465
- id: this.config.llmDeviceId,
466
- name: device.name || this.config.llmDeviceId,
467
- providerType: providerTypeEnum,
468
- lastUsed: 0,
469
- errorCount: 0,
470
- });
471
- this.console.log(`[LLM] Using configured LLM: ${device.name}`);
458
+ // If specific LLM devices are configured, use only those
459
+ if (this.config.llmDeviceIds && this.config.llmDeviceIds.length > 0) {
460
+ for (const deviceId of this.config.llmDeviceIds) {
461
+ const device = systemManager.getDeviceById(deviceId);
462
+ if (device?.interfaces?.includes('ChatCompletion')) {
463
+ const providerTypeEnum = this.detectProviderType(device);
464
+ this.llmDevices.push({
465
+ device: device as unknown as ChatCompletionDevice,
466
+ id: deviceId,
467
+ name: device.name || deviceId,
468
+ providerType: providerTypeEnum,
469
+ lastUsed: 0,
470
+ errorCount: 0,
471
+ });
472
+ this.console.log(`[LLM] Using configured LLM: ${device.name}`);
473
+ } else {
474
+ this.console.warn(`[LLM] Configured device ${deviceId} not found or doesn't support ChatCompletion`);
475
+ }
476
+ }
477
+
478
+ if (this.llmDevices.length > 0) {
479
+ this.console.log(`[LLM] Using ${this.llmDevices.length} configured LLM provider(s)`);
472
480
  return;
473
- } else {
474
- this.console.warn(`[LLM] Configured device ${this.config.llmDeviceId} not found or doesn't support ChatCompletion`);
475
481
  }
482
+ // Fall through to auto-discovery if none of the configured devices worked
483
+ this.console.warn('[LLM] No configured devices available, falling back to auto-discovery');
476
484
  }
477
485
 
478
486
  // Auto-discover all LLM devices for load balancing
@@ -492,7 +500,7 @@ export class SpatialReasoningEngine {
492
500
  errorCount: 0,
493
501
  });
494
502
 
495
- this.console.log(`[LLM] Found: ${device.name}`);
503
+ this.console.log(`[LLM] Auto-discovered: ${device.name}`);
496
504
  }
497
505
  }
498
506
 
@@ -63,8 +63,8 @@ export interface TrackingEngineConfig {
63
63
  objectAlertCooldown: number;
64
64
  /** Use LLM for enhanced descriptions */
65
65
  useLlmDescriptions: boolean;
66
- /** Specific LLM device ID to use (if not set, auto-discovers all for load balancing) */
67
- llmDeviceId?: string;
66
+ /** Specific LLM device IDs to use (if not set, auto-discovers all for load balancing) */
67
+ llmDeviceIds?: string[];
68
68
  /** LLM rate limit interval (ms) - minimum time between LLM calls */
69
69
  llmDebounceInterval?: number;
70
70
  /** Whether to fall back to basic notifications when LLM is unavailable or slow */
@@ -163,7 +163,7 @@ export class TrackingEngine {
163
163
  // Initialize spatial reasoning engine
164
164
  const spatialConfig: SpatialReasoningConfig = {
165
165
  enableLlm: config.useLlmDescriptions,
166
- llmDeviceId: config.llmDeviceId,
166
+ llmDeviceIds: config.llmDeviceIds,
167
167
  enableLandmarkLearning: config.enableLandmarkLearning ?? true,
168
168
  landmarkConfidenceThreshold: config.landmarkConfidenceThreshold ?? 0.7,
169
169
  contextCacheTtl: 60000, // 1 minute cache
package/src/main.ts CHANGED
@@ -238,11 +238,12 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
238
238
  },
239
239
 
240
240
  // Integrations
241
- llmDevice: {
242
- title: 'LLM Provider',
241
+ llmDevices: {
242
+ title: 'LLM Providers',
243
243
  type: 'device',
244
+ multiple: true,
244
245
  deviceFilter: `interfaces.includes('ChatCompletion')`,
245
- description: 'Select the LLM plugin to use for smart descriptions (e.g., OpenAI, Anthropic, Ollama)',
246
+ description: 'Select one or more LLM providers for smart descriptions. Multiple providers will be load-balanced.',
246
247
  group: 'Integrations',
247
248
  },
248
249
  defaultNotifiers: {
@@ -362,7 +363,7 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
362
363
  loiteringThreshold: (this.storageSettings.values.loiteringThreshold as number || 3) * 1000,
363
364
  objectAlertCooldown: (this.storageSettings.values.objectAlertCooldown as number || 30) * 1000,
364
365
  useLlmDescriptions: this.storageSettings.values.useLlmDescriptions as boolean ?? true,
365
- llmDeviceId: this.storageSettings.values.llmDevice as string || undefined,
366
+ llmDeviceIds: this.parseLlmDevices(),
366
367
  llmDebounceInterval: (this.storageSettings.values.llmDebounceInterval as number || 30) * 1000,
367
368
  llmFallbackEnabled: this.storageSettings.values.llmFallbackEnabled as boolean ?? true,
368
369
  llmFallbackTimeout: (this.storageSettings.values.llmFallbackTimeout as number || 3) * 1000,
@@ -417,6 +418,36 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
417
418
  }
418
419
  }
419
420
 
421
+ /** Parse LLM devices from settings - handles both array and single value formats */
422
+ private parseLlmDevices(): string[] | undefined {
423
+ const value = this.storageSettings.values.llmDevices;
424
+ if (!value) return undefined;
425
+
426
+ // Handle array format
427
+ if (Array.isArray(value)) {
428
+ const filtered = value.filter(Boolean);
429
+ return filtered.length > 0 ? filtered : undefined;
430
+ }
431
+
432
+ // Handle JSON string format
433
+ if (typeof value === 'string') {
434
+ try {
435
+ const parsed = JSON.parse(value);
436
+ if (Array.isArray(parsed)) {
437
+ const filtered = parsed.filter(Boolean);
438
+ return filtered.length > 0 ? filtered : undefined;
439
+ }
440
+ // Single device ID string
441
+ return value ? [value] : undefined;
442
+ } catch {
443
+ // Not JSON, treat as single device ID
444
+ return value ? [value] : undefined;
445
+ }
446
+ }
447
+
448
+ return undefined;
449
+ }
450
+
420
451
  // ==================== DeviceProvider Implementation ====================
421
452
 
422
453
  async getDevice(nativeId: string): Promise<any> {
@@ -723,7 +754,7 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
723
754
  key === 'llmDebounceInterval' ||
724
755
  key === 'llmFallbackEnabled' ||
725
756
  key === 'llmFallbackTimeout' ||
726
- key === 'llmDevice' ||
757
+ key === 'llmDevices' ||
727
758
  key === 'enableTransitTimeLearning' ||
728
759
  key === 'enableConnectionSuggestions' ||
729
760
  key === 'enableLandmarkLearning' ||