@blueharford/scrypted-spatial-awareness 0.6.27 → 0.6.29

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.29",
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,19 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
238
238
  },
239
239
 
240
240
  // Integrations
241
- llmDevice: {
242
- title: 'LLM Provider',
241
+ llmPlugin: {
242
+ title: 'LLM Plugin',
243
243
  type: 'device',
244
+ deviceFilter: `interfaces.includes('DeviceProvider') && interfaces.includes('Settings')`,
245
+ description: 'Select the LLM plugin to use (e.g., OpenAI, Anthropic, Ollama from @scrypted/llm)',
246
+ group: 'Integrations',
247
+ },
248
+ llmProviders: {
249
+ title: 'LLM Providers',
250
+ type: 'device',
251
+ multiple: true,
244
252
  deviceFilter: `interfaces.includes('ChatCompletion')`,
245
- description: 'Select the LLM plugin to use for smart descriptions (e.g., OpenAI, Anthropic, Ollama)',
253
+ description: 'Select which providers to use from your LLM plugin. Multiple providers will be load-balanced.',
246
254
  group: 'Integrations',
247
255
  },
248
256
  defaultNotifiers: {
@@ -362,7 +370,7 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
362
370
  loiteringThreshold: (this.storageSettings.values.loiteringThreshold as number || 3) * 1000,
363
371
  objectAlertCooldown: (this.storageSettings.values.objectAlertCooldown as number || 30) * 1000,
364
372
  useLlmDescriptions: this.storageSettings.values.useLlmDescriptions as boolean ?? true,
365
- llmDeviceId: this.storageSettings.values.llmDevice as string || undefined,
373
+ llmDeviceIds: this.parseLlmProviders(),
366
374
  llmDebounceInterval: (this.storageSettings.values.llmDebounceInterval as number || 30) * 1000,
367
375
  llmFallbackEnabled: this.storageSettings.values.llmFallbackEnabled as boolean ?? true,
368
376
  llmFallbackTimeout: (this.storageSettings.values.llmFallbackTimeout as number || 3) * 1000,
@@ -417,6 +425,36 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
417
425
  }
418
426
  }
419
427
 
428
+ /** Parse LLM providers from settings - handles both array and single value formats */
429
+ private parseLlmProviders(): string[] | undefined {
430
+ const value = this.storageSettings.values.llmProviders;
431
+ if (!value) return undefined;
432
+
433
+ // Handle array format
434
+ if (Array.isArray(value)) {
435
+ const filtered = value.filter(Boolean);
436
+ return filtered.length > 0 ? filtered : undefined;
437
+ }
438
+
439
+ // Handle JSON string format
440
+ if (typeof value === 'string') {
441
+ try {
442
+ const parsed = JSON.parse(value);
443
+ if (Array.isArray(parsed)) {
444
+ const filtered = parsed.filter(Boolean);
445
+ return filtered.length > 0 ? filtered : undefined;
446
+ }
447
+ // Single device ID string
448
+ return value ? [value] : undefined;
449
+ } catch {
450
+ // Not JSON, treat as single device ID
451
+ return value ? [value] : undefined;
452
+ }
453
+ }
454
+
455
+ return undefined;
456
+ }
457
+
420
458
  // ==================== DeviceProvider Implementation ====================
421
459
 
422
460
  async getDevice(nativeId: string): Promise<any> {
@@ -723,7 +761,8 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
723
761
  key === 'llmDebounceInterval' ||
724
762
  key === 'llmFallbackEnabled' ||
725
763
  key === 'llmFallbackTimeout' ||
726
- key === 'llmDevice' ||
764
+ key === 'llmPlugin' ||
765
+ key === 'llmProviders' ||
727
766
  key === 'enableTransitTimeLearning' ||
728
767
  key === 'enableConnectionSuggestions' ||
729
768
  key === 'enableLandmarkLearning' ||