@camstack/types 0.1.0 → 0.1.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/index.d.mts CHANGED
@@ -1,5 +1,3 @@
1
- import { M as ModelCatalogEntry, a as ModelFormat, C as CustomModelMetadata, b as ModelOutputFormat, c as ClassMapDefinition, D as DetectionModel, L as LabelDefinition } from './index-B9wf2RhV.mjs';
2
- export { A as ANIMAL_TYPE_MODELS, d as AUDIO_CLASSIFICATION_MODELS, B as BIRD_NABIRDS_MODELS, e as BIRD_SPECIES_MODELS, f as COCO_80_LABELS, g as COCO_TO_MACRO, F as FACE_DETECTION_MODELS, h as FACE_RECOGNITION_MODELS, i as MACRO_LABELS, j as ModelDownloadOptions, k as ModelDownloadResult, l as ModelFormatEntry, O as OBJECT_DETECTION_MODELS, P as PLATE_DETECTION_MODELS, m as PLATE_RECOGNITION_MODELS, S as SEGMENTATION_MODELS } from './index-B9wf2RhV.mjs';
3
1
  import { ChildProcess } from 'node:child_process';
4
2
 
5
3
  interface BoundingBox {
@@ -27,6 +25,18 @@ interface AudioChunkInput {
27
25
  readonly timestamp: number;
28
26
  }
29
27
 
28
+ interface LabelDefinition {
29
+ readonly id: string;
30
+ readonly name: string;
31
+ readonly category?: string;
32
+ readonly description?: string;
33
+ readonly icon?: string;
34
+ }
35
+ interface ClassMapDefinition {
36
+ readonly mapping: Readonly<Record<string, string>>;
37
+ readonly preserveOriginal: boolean;
38
+ }
39
+
30
40
  interface SpatialDetection {
31
41
  readonly class: string;
32
42
  readonly originalClass: string;
@@ -185,6 +195,60 @@ interface KnownAudioEvent {
185
195
  readonly notifyAlways: boolean;
186
196
  }
187
197
 
198
+ type ModelFormat$1 = 'onnx' | 'coreml' | 'openvino' | 'tflite' | 'pt';
199
+ type ModelOutputFormat = 'yolo' | 'ssd' | 'embedding' | 'classification' | 'ocr';
200
+ interface ModelFormatEntry {
201
+ readonly url: string;
202
+ readonly sizeMB: number;
203
+ }
204
+ interface ModelCatalogEntry {
205
+ readonly id: string;
206
+ readonly name: string;
207
+ readonly description: string;
208
+ readonly formats: Partial<Readonly<Record<ModelFormat$1, ModelFormatEntry>>>;
209
+ readonly inputSize: {
210
+ readonly width: number;
211
+ readonly height: number;
212
+ };
213
+ readonly labels: readonly LabelDefinition[];
214
+ readonly inputLayout?: 'nchw' | 'nhwc';
215
+ readonly inputNormalization?: 'zero-one' | 'imagenet' | 'none';
216
+ }
217
+ interface DetectionModel {
218
+ readonly id: string;
219
+ readonly name: string;
220
+ readonly labels: readonly string[];
221
+ readonly inputSize: {
222
+ readonly width: number;
223
+ readonly height: number;
224
+ };
225
+ }
226
+ interface ModelDownloadOptions {
227
+ readonly url: string;
228
+ readonly fallbackUrls?: readonly string[];
229
+ readonly destDir: string;
230
+ readonly filename?: string;
231
+ readonly expectedSha256?: string;
232
+ readonly onProgress?: (downloaded: number, total: number) => void;
233
+ }
234
+ interface ModelDownloadResult {
235
+ readonly filePath: string;
236
+ readonly downloadedBytes: number;
237
+ readonly fromCache: boolean;
238
+ }
239
+ interface CustomModelMetadata {
240
+ readonly name: string;
241
+ readonly inputSize: {
242
+ readonly width: number;
243
+ readonly height: number;
244
+ };
245
+ readonly labels: readonly LabelDefinition[];
246
+ readonly classMap?: ClassMapDefinition;
247
+ readonly inputLayout?: 'nchw' | 'nhwc';
248
+ readonly inputNormalization?: 'zero-one' | 'imagenet' | 'none';
249
+ readonly outputFormat: ModelOutputFormat;
250
+ }
251
+
188
252
  type PipelineSlot = 'detector' | 'cropper' | 'classifier';
189
253
  interface PipelineNode {
190
254
  readonly step: string;
@@ -452,60 +516,147 @@ interface GroundTruth {
452
516
  readonly annotations: readonly GroundTruthAnnotation[];
453
517
  }
454
518
 
519
+ /**
520
+ * Configuration UI Schema -- declarative form definition for any element.
521
+ *
522
+ * Used by addons, providers, and devices to declare their settings UI.
523
+ * The admin UI renders these schemas as responsive forms.
524
+ */
525
+ interface ConfigUISchema {
526
+ sections: ConfigSection[];
527
+ }
528
+ interface ConfigSection {
529
+ id: string;
530
+ title: string;
531
+ description?: string;
532
+ style?: 'card' | 'accordion';
533
+ defaultCollapsed?: boolean;
534
+ columns?: 1 | 2 | 3 | 4;
535
+ fields: ConfigField[];
536
+ }
537
+ type ConfigField = ConfigTextField | ConfigNumberField | ConfigBooleanField | ConfigSelectField | ConfigMultiSelectField | ConfigColorField | ConfigPasswordField | ConfigTextAreaField | ConfigSliderField | ConfigTagsField | ConfigGroupField | ConfigSeparatorField | ConfigInfoField | ConfigModelSelectorField;
455
538
  interface ConfigFieldBase {
456
- readonly key: string;
457
- readonly label: string;
458
- readonly description?: string;
459
- readonly required?: boolean;
460
- readonly dependsOn?: Readonly<Record<string, unknown>>;
539
+ key: string;
540
+ label: string;
541
+ description?: string;
542
+ required?: boolean;
543
+ disabled?: boolean;
544
+ placeholder?: string;
545
+ span?: 1 | 2 | 3 | 4;
546
+ showWhen?: ConfigCondition;
547
+ }
548
+ interface ConfigCondition {
549
+ field: string;
550
+ equals?: unknown;
551
+ notEquals?: unknown;
552
+ in?: unknown[];
553
+ notIn?: unknown[];
461
554
  }
462
555
  interface ConfigTextField extends ConfigFieldBase {
463
- readonly type: 'text';
464
- readonly placeholder?: string;
556
+ type: 'text';
557
+ maxLength?: number;
558
+ pattern?: string;
559
+ inputType?: 'text' | 'url' | 'email';
465
560
  }
466
561
  interface ConfigNumberField extends ConfigFieldBase {
467
- readonly type: 'number';
468
- readonly min?: number;
469
- readonly max?: number;
470
- readonly step?: number;
562
+ type: 'number';
563
+ min?: number;
564
+ max?: number;
565
+ step?: number;
566
+ unit?: string;
471
567
  }
472
568
  interface ConfigBooleanField extends ConfigFieldBase {
473
- readonly type: 'boolean';
569
+ type: 'boolean';
570
+ style?: 'switch' | 'checkbox';
474
571
  }
475
572
  interface ConfigSelectField extends ConfigFieldBase {
476
- readonly type: 'select';
477
- readonly options: ReadonlyArray<{
478
- readonly value: string;
479
- readonly label: string;
480
- }>;
573
+ type: 'select';
574
+ options: ConfigOption[];
575
+ dependsOn?: Record<string, unknown>;
576
+ }
577
+ interface ConfigMultiSelectField extends ConfigFieldBase {
578
+ type: 'multiselect';
579
+ options: ConfigOption[];
580
+ maxItems?: number;
581
+ }
582
+ interface ConfigColorField extends ConfigFieldBase {
583
+ type: 'color';
584
+ presets?: string[];
585
+ }
586
+ interface ConfigPasswordField extends ConfigFieldBase {
587
+ type: 'password';
588
+ showToggle?: boolean;
589
+ }
590
+ interface ConfigTextAreaField extends ConfigFieldBase {
591
+ type: 'textarea';
592
+ rows?: number;
593
+ maxLength?: number;
481
594
  }
482
595
  interface ConfigSliderField extends ConfigFieldBase {
483
- readonly type: 'slider';
484
- readonly min: number;
485
- readonly max: number;
486
- readonly step: number;
487
- readonly default: number;
596
+ type: 'slider';
597
+ min: number;
598
+ max: number;
599
+ step?: number;
600
+ showValue?: boolean;
601
+ unit?: string;
602
+ default?: number;
603
+ }
604
+ interface ConfigTagsField extends ConfigFieldBase {
605
+ type: 'tags';
606
+ suggestions?: string[];
607
+ maxTags?: number;
608
+ }
609
+ interface ConfigGroupField extends ConfigFieldBase {
610
+ type: 'group';
611
+ fields: ConfigField[];
612
+ style?: 'card' | 'inline' | 'accordion';
613
+ defaultCollapsed?: boolean;
614
+ }
615
+ /** Visual separator between fields */
616
+ interface ConfigSeparatorField {
617
+ type: 'separator';
618
+ key: string;
619
+ }
620
+ /** Read-only informational text */
621
+ interface ConfigInfoField {
622
+ type: 'info';
623
+ key: string;
624
+ label: string;
625
+ content: string;
626
+ variant?: 'info' | 'warning' | 'success' | 'danger';
627
+ }
628
+ interface ConfigOption {
629
+ value: string;
630
+ label: string;
631
+ description?: string;
632
+ icon?: string;
488
633
  }
489
634
  interface ConfigModelSelectorField extends ConfigFieldBase {
490
- readonly type: 'model-selector';
491
- readonly catalog: readonly ModelCatalogEntry[];
492
- readonly allowCustom: boolean;
493
- readonly acceptFormats?: readonly ModelFormat[];
494
- readonly allowConversion?: boolean;
495
- readonly requiredMetadata: readonly (keyof CustomModelMetadata)[];
496
- readonly outputFormatHint?: ModelOutputFormat;
497
- }
498
- type ConfigField = ConfigTextField | ConfigNumberField | ConfigBooleanField | ConfigSelectField | ConfigSliderField | ConfigModelSelectorField;
499
- interface ConfigSection {
500
- readonly id: string;
501
- readonly title: string;
502
- readonly description?: string;
503
- readonly style?: 'card' | 'accordion';
504
- readonly columns?: 1 | 2 | 3 | 4;
505
- readonly fields: readonly ConfigField[];
506
- }
507
- interface ConfigUISchema {
508
- readonly sections: readonly ConfigSection[];
635
+ type: 'model-selector';
636
+ /** Filter models by type */
637
+ modelType?: string;
638
+ /** Allow user to enter a custom local path or URL (default: true) */
639
+ allowCustomPath?: boolean;
640
+ /** Allow file upload via UI (default: true) */
641
+ allowUpload?: boolean;
642
+ /** Accepted file extensions for upload (default: ['.onnx']) */
643
+ acceptExtensions?: string[];
644
+ /** URL to model conversion documentation/guide */
645
+ conversionGuideUrl?: string;
646
+ /** Brief text explaining what models are compatible */
647
+ compatibilityNote?: string;
648
+ /** Legacy: inline catalog entries */
649
+ catalog?: readonly ModelCatalogEntry[];
650
+ allowCustom?: boolean;
651
+ acceptFormats?: readonly ModelFormat$1[];
652
+ allowConversion?: boolean;
653
+ requiredMetadata?: readonly (keyof CustomModelMetadata)[];
654
+ outputFormatHint?: ModelOutputFormat;
655
+ }
656
+ interface IConfigurable {
657
+ getConfigSchema(): ConfigUISchema;
658
+ getConfig(): Record<string, unknown>;
659
+ onConfigChange(config: Record<string, unknown>): Promise<void>;
509
660
  }
510
661
 
511
662
  interface RouteRequest {
@@ -581,41 +732,1083 @@ interface TurnServer {
581
732
  readonly credential?: string;
582
733
  }
583
734
 
584
- type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'verbose';
735
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error';
736
+ interface LogEntry {
737
+ timestamp: Date;
738
+ level: LogLevel;
739
+ scope: string[];
740
+ message: string;
741
+ meta?: Record<string, unknown>;
742
+ }
743
+ interface LogFilter {
744
+ scope?: string[];
745
+ level?: LogLevel;
746
+ since?: Date;
747
+ until?: Date;
748
+ limit?: number;
749
+ }
750
+ interface ILogDestination {
751
+ initialize(): Promise<void>;
752
+ shutdown(): Promise<void>;
753
+ write(entry: LogEntry): void;
754
+ query?(filter: LogFilter): Promise<readonly LogEntry[]>;
755
+ }
585
756
  interface IScopedLogger {
586
- error(message: string, ...meta: unknown[]): void;
587
- warn(message: string, ...meta: unknown[]): void;
588
- info(message: string, ...meta: unknown[]): void;
589
- debug(message: string, ...meta: unknown[]): void;
590
- verbose(message: string, ...meta: unknown[]): void;
757
+ debug(message: string, meta?: Record<string, unknown>): void;
758
+ info(message: string, meta?: Record<string, unknown>): void;
759
+ warn(message: string, meta?: Record<string, unknown>): void;
760
+ error(message: string, meta?: Record<string, unknown>): void;
591
761
  child(scope: string): IScopedLogger;
592
762
  }
593
- interface ILogDestination {
763
+ /** Factory that creates scoped loggers */
764
+ type LoggerFactory = {
765
+ createLogger(scope: string): IScopedLogger;
766
+ };
767
+
768
+ interface EventSource {
769
+ type: string;
770
+ id: string;
771
+ }
772
+ interface SystemEvent {
773
+ id: string;
774
+ timestamp: Date;
775
+ source: EventSource;
776
+ category: string;
777
+ data: Record<string, unknown>;
778
+ }
779
+ interface EventFilter {
780
+ source?: EventSource;
781
+ category?: string;
782
+ since?: Date;
783
+ }
784
+ interface IEventBus {
785
+ emit(event: SystemEvent): void;
786
+ subscribe(filter: EventFilter, handler: (event: SystemEvent) => void): () => void;
787
+ getRecent(filter?: EventFilter, limit?: number): readonly SystemEvent[];
788
+ }
789
+
790
+ type StorageLocationName = 'data' | 'media' | 'recordings' | 'models' | 'cache' | 'logs' | 'config' | 'events' | 'addon';
791
+ interface StorageConfig {
792
+ locations: Record<StorageLocationName, string>;
793
+ }
794
+ interface QueryFilter {
795
+ where?: Record<string, unknown>;
796
+ whereIn?: Record<string, unknown[]>;
797
+ whereBetween?: Record<string, [unknown, unknown]>;
798
+ orderBy?: {
799
+ field: string;
800
+ direction: 'asc' | 'desc';
801
+ };
802
+ limit?: number;
803
+ offset?: number;
804
+ }
805
+ interface StorageRecord {
806
+ collection: string;
807
+ id: string;
808
+ data: Record<string, unknown>;
809
+ }
810
+ interface IStructuredStorage {
811
+ query(collection: string, filter?: QueryFilter): Promise<readonly StorageRecord[]>;
812
+ insert(record: StorageRecord): Promise<StorageRecord>;
813
+ update(collection: string, id: string, data: Record<string, unknown>): Promise<StorageRecord>;
814
+ delete(collection: string, id: string): Promise<void>;
815
+ count(collection: string, filter?: QueryFilter): Promise<number>;
816
+ }
817
+ interface IFileStorage {
818
+ readFile(path: string): Promise<Buffer>;
819
+ writeFile(path: string, data: Buffer): Promise<void>;
820
+ deleteFile(path: string): Promise<void>;
821
+ listFiles(prefix?: string): Promise<readonly string[]>;
822
+ getFileUrl(path: string): Promise<string>;
823
+ exists(path: string): Promise<boolean>;
824
+ }
825
+ interface IStorageLocation {
826
+ structured?: IStructuredStorage;
827
+ files?: IFileStorage;
828
+ }
829
+ interface IStorageProvider {
830
+ initialize(): Promise<void>;
831
+ shutdown(): Promise<void>;
832
+ getLocation(name: StorageLocationName): IStorageLocation;
833
+ export(locationName: StorageLocationName): Promise<Buffer>;
834
+ import(locationName: StorageLocationName, data: Buffer): Promise<void>;
835
+ }
836
+
837
+ interface ICameraAnalyticsProvider {
838
+ getLiveState(deviceId: string): CameraLiveState | null;
839
+ getTracks(deviceId: string, filter?: TrackFilter): TrackedObjectSummary[];
840
+ getZoneState(deviceId: string, zoneId: string): ZoneLiveState | null;
841
+ getZoneHistory(deviceId: string, zoneId: string, options: TimeRangeOptions): Promise<ZoneHistoryPoint[]>;
842
+ getHeatmap(deviceId: string, options: HeatmapOptions): Promise<HeatmapData>;
843
+ getTrackDetail(deviceId: string, trackId: string): TrackDetail | null;
844
+ getCameraStatus(deviceId: string): PipelineStatus[];
845
+ }
846
+ interface IAnalysisAddon extends ICamstackAddon, ICameraAnalyticsProvider {
847
+ processFrame(deviceId: string, frame: FrameInput): Promise<DetectionEvent[]>;
848
+ setCameraPipeline(deviceId: string, config: PipelineConfig): void;
849
+ setCameraZones(deviceId: string, zones: ZoneDefinition[]): void;
850
+ setKnownFaces(faces: KnownFace[]): void;
851
+ setKnownPlates(plates: KnownPlate[]): void;
852
+ onLiveStateChange(deviceId: string, callback: (state: CameraLiveState) => void): () => void;
853
+ }
854
+
855
+ /**
856
+ * Result returned by an auth provider after successful authentication.
857
+ */
858
+ interface AuthResult {
859
+ readonly externalId: string;
860
+ readonly username: string;
861
+ readonly email?: string;
862
+ readonly displayName?: string;
863
+ readonly avatarUrl?: string;
864
+ readonly groups?: string[];
865
+ readonly rawProfile?: unknown;
866
+ }
867
+ /**
868
+ * Pluggable authentication provider.
869
+ * Each addon can register one or more auth providers (collection capability).
870
+ */
871
+ interface IAuthProvider {
594
872
  readonly id: string;
595
873
  readonly name: string;
596
- write(level: LogLevel, scope: string, message: string, meta?: unknown): void;
874
+ readonly icon: string;
875
+ /** Type of auth flow */
876
+ readonly flowType: 'credentials' | 'redirect' | 'token';
877
+ /**
878
+ * For 'credentials' flow: validate username/password, return user info.
879
+ */
880
+ validateCredentials?(username: string, password: string): Promise<AuthResult | null>;
881
+ /**
882
+ * For 'redirect' flow (OIDC, OAuth2):
883
+ * getLoginUrl() returns the URL to redirect the user to.
884
+ * handleCallback() processes the callback and returns user info.
885
+ */
886
+ getLoginUrl?(state: string): string;
887
+ handleCallback?(params: Record<string, string>): Promise<AuthResult>;
888
+ /**
889
+ * For 'token' flow: validate an external token (e.g., from mobile app).
890
+ */
891
+ validateToken?(token: string): Promise<AuthResult | null>;
597
892
  }
598
893
 
599
- type EventCallback<T = unknown> = (data: T) => void;
600
- interface IEventBus {
601
- on<T = unknown>(event: string, callback: EventCallback<T>): () => void;
602
- once<T = unknown>(event: string, callback: EventCallback<T>): () => void;
603
- emit<T = unknown>(event: string, data: T): void;
604
- removeAllListeners(event?: string): void;
894
+ /**
895
+ * A notification to be sent through configured output channels.
896
+ */
897
+ interface Notification {
898
+ readonly title: string;
899
+ readonly message: string;
900
+ readonly severity: 'info' | 'warning' | 'critical';
901
+ readonly category: string;
902
+ readonly data?: Record<string, unknown>;
903
+ readonly imageUrl?: string;
904
+ readonly deviceId?: string;
905
+ readonly timestamp: number;
906
+ }
907
+ /**
908
+ * A toast notification for the admin UI.
909
+ */
910
+ interface Toast {
911
+ readonly title: string;
912
+ readonly message: string;
913
+ readonly severity: 'info' | 'warning' | 'critical';
914
+ readonly duration?: number;
915
+ readonly action?: {
916
+ readonly label: string;
917
+ readonly url: string;
918
+ };
919
+ }
920
+ /**
921
+ * Service interface for broadcasting toast notifications to connected clients.
922
+ */
923
+ interface IToastService {
924
+ broadcast(toast: Toast): void;
925
+ sendToUser(userId: string, toast: Toast): void;
926
+ }
927
+ /**
928
+ * Pluggable notification output channel.
929
+ * Each addon implements a notification channel (Telegram, webhook, email, etc.).
930
+ */
931
+ interface INotificationOutput {
932
+ readonly id: string;
933
+ readonly name: string;
934
+ readonly icon: string;
935
+ /** Send a notification */
936
+ send(notification: Notification): Promise<void>;
937
+ /** Test the notification channel */
938
+ sendTest?(): Promise<{
939
+ success: boolean;
940
+ error?: string;
941
+ }>;
942
+ /** Config schema for the admin UI */
943
+ getConfigSchema?(): ConfigUISchema;
605
944
  }
606
945
 
607
- type StorageLocationName = 'data' | 'media' | 'recordings' | 'models' | 'cache' | 'logs';
608
- interface IStorageBackend {
609
- resolve(location: StorageLocationName, ...segments: string[]): string;
610
- isAvailable(location: StorageLocationName): boolean;
946
+ /**
947
+ * Scope restriction for a scoped token.
948
+ */
949
+ interface TokenScope {
950
+ readonly type: 'addon' | 'route-prefix' | 'capability';
951
+ readonly target: string;
952
+ }
953
+ /**
954
+ * A per-user API token with restricted scopes.
955
+ */
956
+ interface ScopedToken {
957
+ readonly id: string;
958
+ readonly userId: string;
959
+ readonly name: string;
960
+ readonly tokenHash: string;
961
+ readonly tokenPrefix: string;
962
+ readonly scopes: TokenScope[];
963
+ readonly expiresAt?: number;
964
+ readonly lastUsedAt?: number;
965
+ readonly createdAt: number;
966
+ }
967
+
968
+ type RouteAccess = 'public' | 'authenticated' | 'admin';
969
+ /**
970
+ * Request object passed to addon route handlers.
971
+ */
972
+ interface AddonHttpRequest {
973
+ readonly params: Record<string, string>;
974
+ readonly query: Record<string, string>;
975
+ readonly body: unknown;
976
+ readonly headers: Record<string, string>;
977
+ /** Populated for 'authenticated' and 'admin' routes */
978
+ readonly user?: {
979
+ readonly id: string;
980
+ readonly username: string;
981
+ readonly role: string;
982
+ };
983
+ /** Populated when request uses a scoped token */
984
+ readonly scopedToken?: {
985
+ readonly id: string;
986
+ readonly userId: string;
987
+ readonly scopes: TokenScope[];
988
+ };
989
+ }
990
+ /**
991
+ * Reply object for addon route handlers.
992
+ */
993
+ interface AddonHttpReply {
994
+ status(code: number): AddonHttpReply;
995
+ send(data: unknown): void;
996
+ redirect(url: string): void;
997
+ header(name: string, value: string): AddonHttpReply;
998
+ }
999
+ /**
1000
+ * A single HTTP route registered by an addon.
1001
+ */
1002
+ interface IAddonHttpRoute {
1003
+ readonly method: 'GET' | 'POST' | 'PUT' | 'DELETE';
1004
+ readonly path: string;
1005
+ readonly access: RouteAccess;
1006
+ readonly description?: string;
1007
+ readonly handler: (request: AddonHttpRequest, reply: AddonHttpReply) => Promise<void>;
1008
+ }
1009
+ /**
1010
+ * Provider interface for addons that expose HTTP endpoints.
1011
+ */
1012
+ interface IAddonRouteProvider {
1013
+ readonly id: string;
1014
+ getRoutes(): IAddonHttpRoute[];
1015
+ }
1016
+
1017
+ /**
1018
+ * Camera Analysis Pipeline -- unified stream processing.
1019
+ */
1020
+ interface IStreamSourceAdapter {
1021
+ protocol: string;
1022
+ connect(config: Record<string, unknown>): Promise<NormalizedSource>;
1023
+ disconnect(): Promise<void>;
1024
+ getStatus(): SourceAdapterStatus;
1025
+ }
1026
+ interface NormalizedSource {
1027
+ type: 'url' | 'pipe';
1028
+ url?: string;
1029
+ pipe?: NodeJS.ReadableStream;
1030
+ capabilities: SourceCapabilities;
1031
+ }
1032
+ interface SourceCapabilities {
1033
+ hasVideo: boolean;
1034
+ hasAudio: boolean;
1035
+ videoCodec?: 'h264' | 'h265' | 'mjpeg' | 'vp8' | 'vp9' | 'av1';
1036
+ audioCodec?: 'aac' | 'opus' | 'pcm' | 'g711';
1037
+ width?: number;
1038
+ height?: number;
1039
+ fps?: number;
1040
+ sampleRate?: number;
1041
+ audioChannels?: number;
1042
+ }
1043
+ interface SourceAdapterStatus {
1044
+ connected: boolean;
1045
+ protocol: string;
1046
+ error?: string;
1047
+ bytesReceived?: number;
1048
+ connectedSince?: number;
1049
+ }
1050
+ interface VideoFrame {
1051
+ data: Buffer;
1052
+ format: 'jpeg' | 'raw-yuv420' | 'raw-rgb' | 'h264-nal';
1053
+ width: number;
1054
+ height: number;
1055
+ pts: number;
1056
+ timestamp: number;
1057
+ keyframe?: boolean;
1058
+ }
1059
+ interface AudioChunk {
1060
+ data: Buffer;
1061
+ format: 'pcm-s16le' | 'pcm-f32le' | 'aac' | 'opus';
1062
+ sampleRate: number;
1063
+ channels: number;
1064
+ pts: number;
1065
+ timestamp: number;
1066
+ dBFS?: number;
1067
+ }
1068
+ interface ICameraPipeline {
1069
+ readonly deviceId: string;
1070
+ readonly active: boolean;
1071
+ start(): Promise<void>;
1072
+ stop(): Promise<void>;
1073
+ onVideoFrame(callback: VideoFrameCallback, options?: FrameSubscriptionOptions): () => void;
1074
+ getLatestFrame(): VideoFrame | null;
1075
+ onAudioChunk(callback: AudioChunkCallback): () => void;
1076
+ getAudioLevel(): number;
1077
+ getOutputStream(format: PipelineOutputFormat): PipelineOutputStream | null;
1078
+ getAvailableOutputs(): PipelineOutputFormat[];
1079
+ getStatus(): PipelineStatusInfo;
1080
+ }
1081
+ type VideoFrameCallback = (frame: VideoFrame) => void;
1082
+ type AudioChunkCallback = (chunk: AudioChunk) => void;
1083
+ interface FrameSubscriptionOptions {
1084
+ maxFps?: number;
1085
+ format?: 'jpeg' | 'raw-yuv420' | 'raw-rgb';
1086
+ maxWidth?: number;
1087
+ maxHeight?: number;
1088
+ keyframeOnly?: boolean;
1089
+ }
1090
+ type PipelineOutputFormat = 'webrtc' | 'hls' | 'mjpeg' | 'rtsp' | 'raw-h264';
1091
+ interface PipelineOutputStream {
1092
+ format: PipelineOutputFormat;
1093
+ url?: string;
1094
+ createOffer?(): Promise<string>;
1095
+ handleAnswer?(sdp: string): Promise<void>;
1096
+ pipe?: NodeJS.ReadableStream;
1097
+ consumerCount: number;
1098
+ }
1099
+ interface PipelineStatusInfo {
1100
+ active: boolean;
1101
+ source: {
1102
+ protocol: string;
1103
+ connected: boolean;
1104
+ capabilities: SourceCapabilities;
1105
+ } | null;
1106
+ decode: {
1107
+ fps: number;
1108
+ droppedFrames: number;
1109
+ totalFrames: number;
1110
+ };
1111
+ videoConsumers: number;
1112
+ audioConsumers: number;
1113
+ outputs: Array<{
1114
+ format: PipelineOutputFormat;
1115
+ consumers: number;
1116
+ active: boolean;
1117
+ }>;
1118
+ uptime: number;
1119
+ }
1120
+ interface IPipelineManager {
1121
+ getPipeline(deviceId: string): ICameraPipeline | null;
1122
+ createPipeline(deviceId: string, source: NormalizedSource): ICameraPipeline;
1123
+ destroyPipeline(deviceId: string): Promise<void>;
1124
+ listPipelines(): Array<{
1125
+ deviceId: string;
1126
+ status: PipelineStatusInfo;
1127
+ }>;
1128
+ }
1129
+ interface IPipelineConsumer {
1130
+ id: string;
1131
+ name: string;
1132
+ videoRequirements?: FrameSubscriptionOptions;
1133
+ needsAudio: boolean;
1134
+ attachToPipeline(pipeline: ICameraPipeline, deviceId: string): void;
1135
+ detachFromPipeline(deviceId: string): void;
1136
+ }
1137
+
1138
+ interface IDeviceCapability {
1139
+ kind: DeviceCapabilityName;
1140
+ }
1141
+ type DeviceCapabilityName = 'camera' | 'panTiltZoom' | 'motionSensor' | 'objectDetector' | 'audioDetector' | 'twoWayAudio' | 'doorbell' | 'events' | 'recording' | 'accessory' | 'statusLight' | 'siren' | 'switch';
1142
+ interface CapabilityBinding {
1143
+ source: 'native' | 'addon' | 'disabled';
1144
+ addonId?: string;
1145
+ config?: Record<string, unknown>;
1146
+ }
1147
+ interface DeviceCapabilityBinding {
1148
+ deviceId: string;
1149
+ bindings: Partial<Record<DeviceCapabilityName, CapabilityBinding>>;
1150
+ }
1151
+
1152
+ /** A single detection result from a frame */
1153
+ interface Detection {
1154
+ className: string;
1155
+ score: number;
1156
+ boundingBox?: [number, number, number, number];
1157
+ id?: string;
1158
+ label?: string;
1159
+ labelScore?: number;
1160
+ zones?: string[];
1161
+ previousZones?: string[];
1162
+ }
1163
+ /** Payload emitted by onDetections -- a full frame of detections */
1164
+ interface DetectionFrame {
1165
+ detections: Detection[];
1166
+ frameWidth: number;
1167
+ frameHeight: number;
1168
+ timestamp: number;
1169
+ }
1170
+ /** An actively tracked object in the scene */
1171
+ interface ActiveDetection extends Detection {
1172
+ state: 'moving' | 'stationary';
1173
+ durationMs: number;
1174
+ velocity?: {
1175
+ dx: number;
1176
+ dy: number;
1177
+ };
1178
+ }
1179
+ /** A closed polygon zone on the camera scene */
1180
+ interface DetectionZone {
1181
+ id: string;
1182
+ name: string;
1183
+ polygon: Array<[number, number]>;
1184
+ mode: 'observe' | 'restrict';
1185
+ }
1186
+ /** A line on the scene for crossing rules */
1187
+ interface DetectionLine {
1188
+ id: string;
1189
+ name: string;
1190
+ points: [[number, number], [number, number]];
1191
+ direction: 'left-to-right' | 'right-to-left' | 'both';
1192
+ }
1193
+ interface IObjectDetector extends IDeviceCapability {
1194
+ kind: 'objectDetector';
1195
+ getLabels(): string[];
1196
+ onDetections(callback: (frame: DetectionFrame) => void): () => void;
1197
+ getZones(): Promise<DetectionZone[]>;
1198
+ addZone(zone: DetectionZone): Promise<void>;
1199
+ removeZone(zoneId: string): Promise<void>;
1200
+ updateZone(zoneId: string, zone: Partial<Omit<DetectionZone, 'id'>>): Promise<void>;
1201
+ getLines(): Promise<DetectionLine[]>;
1202
+ addLine(line: DetectionLine): Promise<void>;
1203
+ removeLine(lineId: string): Promise<void>;
1204
+ getActiveDetections(): ActiveDetection[];
1205
+ getZoneDetections(zoneId: string): ActiveDetection[];
1206
+ getStationaryDetections(): ActiveDetection[];
1207
+ getMovingDetections(): ActiveDetection[];
1208
+ }
1209
+
1210
+ /**
1211
+ * Detection Analysis Pipeline -- processes raw detections through
1212
+ * configurable stages to produce tracked, enriched, actionable events.
1213
+ */
1214
+
1215
+ declare const DETECTION_TYPES: readonly ["person", "vehicle", "animal", "package"];
1216
+ type DetectionType = typeof DETECTION_TYPES[number];
1217
+ declare const SUB_DETECTION_TYPES: readonly ["face", "plate"];
1218
+ type SubDetectionType = typeof SUB_DETECTION_TYPES[number];
1219
+ declare const RECOGNITION_TYPES: readonly ["face", "plate", "clip", "custom"];
1220
+ type RecognitionType = typeof RECOGNITION_TYPES[number];
1221
+ interface AnalysisContext {
1222
+ readonly deviceId: string;
1223
+ readonly frame: VideoFrame;
1224
+ readonly timestamp: number;
1225
+ readonly rawDetections: readonly Detection[];
1226
+ readonly trackedDetections: readonly ServerTrackedDetection[];
1227
+ readonly events: readonly AnalysisEvent[];
1228
+ readonly metadata: Readonly<Record<string, unknown>>;
1229
+ }
1230
+ interface ServerTrackedDetection {
1231
+ readonly trackId: string;
1232
+ readonly detection: Detection;
1233
+ readonly crop?: Buffer;
1234
+ readonly tracking: TrackingInfo;
1235
+ readonly subDetections: readonly SubDetection[];
1236
+ readonly recognitions: readonly RecognitionResult[];
1237
+ readonly zones: readonly string[];
1238
+ readonly previousZones: readonly string[];
1239
+ }
1240
+ interface TrackingInfo {
1241
+ readonly age: number;
1242
+ readonly state: 'moving' | 'stationary' | 'new' | 'lost';
1243
+ readonly stationaryDuration: number;
1244
+ readonly velocity: {
1245
+ readonly dx: number;
1246
+ readonly dy: number;
1247
+ };
1248
+ readonly positionHistory: ReadonlyArray<{
1249
+ readonly x: number;
1250
+ readonly y: number;
1251
+ readonly t: number;
1252
+ }>;
1253
+ }
1254
+ interface SubDetection {
1255
+ readonly detectionType: SubDetectionType;
1256
+ readonly boundingBox: readonly [number, number, number, number];
1257
+ readonly score: number;
1258
+ readonly crop?: Buffer;
1259
+ }
1260
+ interface RecognitionResult {
1261
+ readonly recognitionType: RecognitionType;
1262
+ readonly label: string;
1263
+ readonly score: number;
1264
+ readonly embedding?: readonly number[];
1265
+ readonly metadata?: Readonly<Record<string, unknown>>;
1266
+ }
1267
+ interface AnalysisEvent {
1268
+ readonly category: string;
1269
+ readonly severity: 'info' | 'warning' | 'alert';
1270
+ readonly detection: ServerTrackedDetection;
1271
+ readonly description: string;
1272
+ readonly data: Readonly<Record<string, unknown>>;
1273
+ }
1274
+ interface IAnalysisStage {
1275
+ readonly id: string;
1276
+ readonly name: string;
1277
+ readonly priority: number;
1278
+ enabled: boolean;
1279
+ process(ctx: AnalysisContext): Promise<AnalysisContext>;
1280
+ }
1281
+ interface ISubDetector {
1282
+ readonly detectionType: SubDetectionType;
1283
+ readonly targetClasses: readonly string[];
1284
+ detect(crop: Buffer, cropWidth: number, cropHeight: number): Promise<SubDetection[]>;
1285
+ }
1286
+ interface IFaceDetector extends ISubDetector {
1287
+ readonly detectionType: 'face';
1288
+ }
1289
+ interface IPlateDetector extends ISubDetector {
1290
+ readonly detectionType: 'plate';
1291
+ }
1292
+ interface IRecognizer {
1293
+ readonly recognitionType: RecognitionType;
1294
+ recognize(crop: Buffer, width: number, height: number): Promise<RecognitionResult | null>;
1295
+ }
1296
+ interface IFaceRecognizer extends IRecognizer {
1297
+ getEmbedding(crop: Buffer): Promise<Float32Array>;
1298
+ registerKnown(label: string, embedding: Float32Array): void;
1299
+ }
1300
+ interface IPlateRecognizer extends IRecognizer {
1301
+ readonly recognitionType: 'plate';
1302
+ }
1303
+ interface IAudioClassifier {
1304
+ readonly ready: boolean;
611
1305
  initialize(): Promise<void>;
612
- getLocationPath(location: StorageLocationName): string;
1306
+ classify(audioSamples: Float32Array, sampleRate: number, topN?: number): Promise<readonly AudioClassification[]>;
1307
+ release(): Promise<void>;
613
1308
  }
614
- interface IStorageProvider {
615
- getLocation(name: string): string;
616
- setLocationPath(name: StorageLocationName, absolutePath: string): void;
1309
+ interface AudioClassification {
1310
+ readonly classIndex: number;
1311
+ readonly className: string;
1312
+ readonly score: number;
1313
+ readonly isSurveillanceRelevant: boolean;
1314
+ }
1315
+ interface IClassFilterStage extends IAnalysisStage {
1316
+ readonly id: 'class-filter';
1317
+ setConfig(deviceId: string, config: Record<string, unknown>): void;
1318
+ getConfig(deviceId: string): Record<string, unknown>;
1319
+ }
1320
+ interface ITrackerStage extends IAnalysisStage {
1321
+ readonly id: 'tracker';
1322
+ setConfig(deviceId: string, config: Record<string, unknown>): void;
1323
+ getConfig(deviceId: string): Record<string, unknown>;
1324
+ resetDevice(deviceId: string): void;
1325
+ }
1326
+ interface ISubDetectionStage extends IAnalysisStage {
1327
+ readonly id: 'sub-detection';
1328
+ registerDetector(detector: ISubDetector): void;
1329
+ }
1330
+ interface IRecognitionStage extends IAnalysisStage {
1331
+ readonly id: 'recognition';
1332
+ registerRecognizer(recognizer: IRecognizer): void;
1333
+ }
1334
+ interface IZoneAnalysisStage extends IAnalysisStage {
1335
+ readonly id: 'zone-analysis';
1336
+ setConfig(deviceId: string, config: Record<string, unknown>): void;
1337
+ }
1338
+ interface IEventGenerationStage extends IAnalysisStage {
1339
+ readonly id: 'event-generation';
1340
+ setConfig(deviceId: string, config: Record<string, unknown>): void;
1341
+ }
1342
+ interface IObjectSnapshotStage extends IAnalysisStage {
1343
+ readonly id: 'object-snapshot';
1344
+ setConfig(deviceId: string, config: Record<string, unknown>): void;
1345
+ }
1346
+ interface IAnalysisPipeline {
1347
+ addStage(stage: IAnalysisStage): void;
1348
+ removeStage(stageId: string): void;
1349
+ getStages(): readonly IAnalysisStage[];
1350
+ analyze(deviceId: string, frame: VideoFrame, rawDetections: Detection[]): Promise<AnalysisContext>;
1351
+ }
1352
+ declare function createAnalysisContext(deviceId: string, frame: VideoFrame, rawDetections: Detection[]): AnalysisContext;
1353
+ declare function enrichContext(ctx: AnalysisContext, updates: Partial<Pick<AnalysisContext, 'trackedDetections' | 'events' | 'metadata'>>): AnalysisContext;
1354
+
1355
+ interface ObjectSnapshotResult {
1356
+ readonly trackId: string;
1357
+ readonly thumbnail?: Buffer;
1358
+ readonly fullCrop?: Buffer;
1359
+ }
1360
+ interface AnnotatedSnapshotResult {
1361
+ readonly thumbnail: Buffer;
1362
+ readonly full: Buffer;
1363
+ }
1364
+ interface PersistableEvent {
1365
+ readonly id: string;
1366
+ readonly timestamp: number;
1367
+ readonly deviceId: string;
1368
+ readonly category: string;
1369
+ readonly className: string;
1370
+ readonly score: number;
1371
+ readonly trackId: string;
1372
+ readonly severity: string;
1373
+ readonly description: string;
1374
+ readonly data: Record<string, unknown>;
1375
+ readonly mediaFiles: readonly string[];
1376
+ }
1377
+ interface EventBufferStatus {
1378
+ readonly eventCount: number;
1379
+ readonly mediaCount: number;
1380
+ readonly mediaSizeMB: number;
1381
+ }
1382
+ type TrackMediaType = 'crop-thumb' | 'crop-full' | 'debug-annotated-thumb' | 'debug-annotated-full' | 'original' | 'original-thumb' | 'inline-crop' | 'unknown';
1383
+ interface TrackMediaFile {
1384
+ readonly path: string;
1385
+ readonly type: TrackMediaType;
1386
+ readonly data: Buffer;
1387
+ readonly source: 'buffer' | 'storage';
1388
+ }
1389
+ interface TrackCaptureConfig {
1390
+ readonly enabled: boolean;
1391
+ readonly snapshotIntervalMs: number;
1392
+ readonly maxTrailLength: number;
1393
+ readonly saveThumbnails: boolean;
1394
+ readonly thumbnailSize: {
1395
+ readonly width: number;
1396
+ readonly height: number;
1397
+ };
1398
+ }
1399
+ interface TrackPosition {
1400
+ readonly x: number;
1401
+ readonly y: number;
1402
+ readonly timestamp: number;
1403
+ readonly bbox: readonly [number, number, number, number];
1404
+ }
1405
+ interface TrackSnapshot {
1406
+ readonly timestamp: number;
1407
+ readonly position: TrackPosition;
1408
+ readonly thumbnailPath: string;
1409
+ }
1410
+ interface TrackTrail {
1411
+ readonly trackId: string;
1412
+ readonly deviceId: string;
1413
+ readonly className: string;
1414
+ readonly label?: string;
1415
+ readonly firstSeen: number;
1416
+ readonly lastSeen: number;
1417
+ readonly positions: readonly TrackPosition[];
1418
+ readonly snapshots: readonly TrackSnapshot[];
1419
+ readonly totalDistance: number;
1420
+ readonly zonesVisited: readonly string[];
1421
+ readonly active: boolean;
1422
+ }
1423
+ interface RetentionConfig {
1424
+ readonly cleanupIntervalMs: number;
1425
+ readonly detectionEventsDays: number;
1426
+ readonly audioLevelsDays: number;
1427
+ /** @deprecated — snapshots are tied to events, use detectionEventsDays */
1428
+ readonly snapshotsDays: number;
1429
+ readonly deviceOverrides?: Readonly<Record<string, Partial<Pick<RetentionConfig, 'detectionEventsDays' | 'audioLevelsDays' | 'snapshotsDays'>>>>;
1430
+ }
1431
+ declare const DEFAULT_RETENTION: RetentionConfig;
1432
+ interface RetentionReport {
1433
+ readonly deletedEvents: number;
1434
+ readonly deletedAudioRecords: number;
1435
+ readonly deletedSnapshots: number;
1436
+ }
1437
+ interface SessionTrack {
1438
+ readonly trackId: string;
1439
+ readonly deviceId: string;
1440
+ readonly className: string;
1441
+ readonly label?: string;
1442
+ readonly firstSeen: number;
1443
+ readonly lastSeen: number;
1444
+ readonly totalFrames: number;
1445
+ readonly lastDetection: ServerTrackedDetection;
1446
+ readonly state: string;
1447
+ readonly positions: ReadonlyArray<{
1448
+ readonly x: number;
1449
+ readonly y: number;
1450
+ readonly t: number;
1451
+ }>;
1452
+ readonly embedding?: Float32Array;
1453
+ readonly globalId?: string;
1454
+ readonly bestCrop?: Buffer;
1455
+ }
1456
+ interface GlobalIdentity {
1457
+ readonly globalId: string;
1458
+ readonly embedding: Float32Array;
1459
+ readonly label?: string;
1460
+ readonly firstSeen: number;
1461
+ readonly lastSeen: number;
1462
+ readonly deviceIds: ReadonlyArray<string>;
1463
+ }
1464
+ interface ClipRecognizer {
1465
+ getEmbedding(imageBuffer: Buffer): Promise<Float32Array>;
1466
+ }
1467
+ interface KnownFaceEntry {
1468
+ readonly id: string;
1469
+ readonly label: string;
1470
+ readonly group?: string;
1471
+ readonly embedding: readonly number[];
1472
+ readonly cropBase64: string;
1473
+ readonly createdAt: number;
1474
+ readonly updatedAt: number;
1475
+ readonly source?: string;
1476
+ readonly metadata?: Readonly<Record<string, unknown>>;
1477
+ }
1478
+ /**
1479
+ * Interface for the recording addon — the server uses this instead of
1480
+ * importing PipelineAddon directly from addon-pipeline.
1481
+ */
1482
+ interface IRecordingAddon {
1483
+ getCoordinator(): IRecordingCoordinator;
1484
+ getRecordingDb(): IRecordingDb;
1485
+ }
1486
+ /**
1487
+ * Minimal coordinator interface for the server's recording router.
1488
+ */
1489
+ interface IRecordingCoordinator {
1490
+ enableRecording(deviceId: string, options: {
1491
+ policy: unknown;
1492
+ ffmpegOverrides?: unknown;
1493
+ }): Promise<void>;
1494
+ disableRecording(deviceId: string): Promise<void>;
1495
+ isRecording(deviceId: string): boolean;
1496
+ readonly playlistGenerator: {
1497
+ generate(deviceId: string, streamId: string, startTime: number, endTime: number, options?: {
1498
+ live?: boolean;
1499
+ }): unknown;
1500
+ };
1501
+ readonly storageEstimator: {
1502
+ estimateForDevice(deviceId: string, motionInput?: {
1503
+ avgEventsPerDay: number;
1504
+ avgDurationSec: number;
1505
+ }): {
1506
+ totalEstimatedGb: number;
1507
+ [key: string]: unknown;
1508
+ };
1509
+ };
1510
+ }
1511
+ /**
1512
+ * Minimal recording database interface for the server's recording router.
1513
+ */
1514
+ interface IRecordingDb {
1515
+ upsertStorageConfig(config: {
1516
+ deviceId: string;
1517
+ dataCategory: string;
1518
+ storageName: string;
1519
+ subDirectory: string;
1520
+ retentionDays: number | null;
1521
+ retentionGb: number | null;
1522
+ }): void;
1523
+ getPolicy(deviceId: string): unknown;
1524
+ getEnabledPolicies(): Array<{
1525
+ deviceId: string;
1526
+ [key: string]: unknown;
1527
+ }>;
1528
+ querySegments(deviceId: string, streamId: string, startTime: number, endTime: number): unknown;
1529
+ getAvailability(deviceId: string, startTime: number, endTime: number): unknown;
1530
+ getStorageUsage(deviceId: string, streamId: string): unknown;
1531
+ upsertPolicy(policy: unknown): void;
1532
+ findNearestThumbnail(deviceId: string, timestamp: number, category: string): unknown;
1533
+ getMotionStats(deviceId: string, startTime: number, endTime: number): {
1534
+ avgEventsPerDay: number;
1535
+ avgDurationSec: number;
1536
+ dutyCyclePercent: number;
1537
+ totalEvents: number;
1538
+ };
1539
+ resolveStorageConfig(deviceId: string, dataCategory: string): unknown;
1540
+ }
1541
+ interface IEventPersistence {
1542
+ start(): void;
1543
+ stop(): void;
1544
+ saveDetectionCrops(eventId: string, crops: readonly ObjectSnapshotResult[]): void;
1545
+ saveAnnotatedFrame(eventId: string, result: AnnotatedSnapshotResult): void;
1546
+ saveOriginalFrame(eventId: string, frame: VideoFrame): Promise<void>;
1547
+ getEventMedia(eventId: string): Promise<unknown>;
1548
+ getTrackMedia(trackId: string): Promise<unknown>;
1549
+ getDeviceMedia(deviceId: string, since?: number, until?: number): Promise<unknown>;
1550
+ getBufferStatus(): unknown;
1551
+ flush(): Promise<void>;
1552
+ }
1553
+ interface ITrackTrail {
1554
+ setConfig(deviceId: string, config: Partial<TrackCaptureConfig>): void;
1555
+ getConfig(deviceId: string): unknown;
1556
+ recordFrame(ctx: unknown): Promise<void>;
1557
+ getActiveTrail(trackId: string): unknown;
1558
+ getActiveTrails(deviceId: string): unknown;
1559
+ getPersistedTrail(trackId: string): Promise<unknown>;
1560
+ getTrail(trackId: string): Promise<unknown>;
1561
+ listTrails(deviceId: string, options?: {
1562
+ since?: number;
1563
+ until?: number;
1564
+ limit?: number;
1565
+ className?: string;
1566
+ }): Promise<unknown>;
1567
+ }
1568
+ interface IRetention {
1569
+ start(): void;
1570
+ stop(): void;
1571
+ runCleanup(): Promise<unknown>;
1572
+ setConfig(update: Partial<RetentionConfig>): void;
1573
+ getConfig(): unknown;
1574
+ forceCleanup(): Promise<unknown>;
1575
+ }
1576
+ interface ISessionTracker {
1577
+ updateFromAnalysis(deviceId: string, ctx: unknown): void;
1578
+ getActiveTracks(deviceId: string): unknown;
1579
+ getAllActiveTracks(): unknown;
1580
+ getTrack(deviceId: string, trackId: string): unknown;
1581
+ getTrackCounts(): unknown;
1582
+ clearDevice(deviceId: string): void;
1583
+ clearAll(): void;
1584
+ getGlobalPool(): unknown;
1585
+ }
1586
+ interface IKnownFaces {
1587
+ register(entry: KnownFaceEntry): Promise<void>;
1588
+ listAll(): Promise<unknown>;
1589
+ delete(id: string): Promise<void>;
1590
+ update(id: string, updates: Partial<Pick<KnownFaceEntry, 'label' | 'group'>>): Promise<void>;
1591
+ recalculateEmbedding(id: string, clipRecognizer: ClipRecognizer): Promise<void>;
1592
+ findMatch(embedding: Float32Array, threshold: number): Promise<unknown>;
1593
+ batchRegister(entries: ReadonlyArray<{
1594
+ readonly label: string;
1595
+ readonly cropBase64: string;
1596
+ readonly group?: string;
1597
+ }>, clipRecognizer: ClipRecognizer): Promise<unknown>;
1598
+ }
1599
+ /**
1600
+ * Composite interface exposed via CapabilityRegistry as the
1601
+ * 'analysis-data-persistence' singleton. Each sub-service is wired
1602
+ * individually to the corresponding NestJS wrapper service.
1603
+ */
1604
+ interface IAnalysisDataPersistence {
1605
+ readonly eventPersistence: IEventPersistence;
1606
+ readonly knownFaces: IKnownFaces;
1607
+ readonly sessionTracker: ISessionTracker;
1608
+ readonly retention: IRetention;
1609
+ readonly trackTrail: ITrackTrail;
1610
+ }
1611
+ interface TestConnectionResult {
1612
+ readonly success: boolean;
1613
+ readonly version?: string;
1614
+ readonly cameraCount?: number;
1615
+ readonly error?: string;
1616
+ }
1617
+ /**
1618
+ * Generic interface for testing provider connections.
1619
+ * Each provider addon can expose a connection tester.
1620
+ */
1621
+ interface IProviderConnectionTester {
1622
+ testConnection(): Promise<TestConnectionResult>;
1623
+ }
1624
+
1625
+ /**
1626
+ * Whether a capability allows one active provider (singleton) or many (collection).
1627
+ */
1628
+ type CapabilityMode = 'singleton' | 'collection';
1629
+ /**
1630
+ * Declared in addon package.json manifests under camstack.addons[].capabilities.
1631
+ */
1632
+ interface CapabilityDeclaration {
1633
+ /** Capability name (e.g. 'storage', 'decoder', 'analysis-pipeline') */
1634
+ readonly name: string;
1635
+ /** singleton: one active at a time. collection: all active in parallel. */
1636
+ readonly mode: CapabilityMode;
1637
+ /** Optional dependencies — addon won't initialize until these capabilities are available */
1638
+ readonly dependsOn?: readonly string[];
1639
+ }
1640
+ /**
1641
+ * Registration object for a service that consumes a capability.
1642
+ * The registry calls the appropriate callback when providers are added/removed/replaced.
1643
+ */
1644
+ interface CapabilityConsumerRegistration<T = unknown> {
1645
+ /** Which capability this consumer needs */
1646
+ readonly capability: string;
1647
+ /** Called when a singleton provider is set or replaced. Async for teardown of previous. */
1648
+ onSet?(provider: T): void | Promise<void>;
1649
+ /** Called when a collection provider is added */
1650
+ onAdded?(provider: T): void | Promise<void>;
1651
+ /** Called when a collection provider is removed (addon disabled) */
1652
+ onRemoved?(provider: T): void | Promise<void>;
1653
+ }
1654
+ /**
1655
+ * Introspection data for a registered capability.
1656
+ */
1657
+ interface CapabilityInfo {
1658
+ readonly name: string;
1659
+ readonly mode: CapabilityMode;
1660
+ /** All addon IDs that provide this capability */
1661
+ readonly providers: readonly string[];
1662
+ /** For singleton: the active addon ID (null if none) */
1663
+ readonly activeProvider: string | null;
1664
+ }
1665
+ /**
1666
+ * Maps capability names to their provider interfaces.
1667
+ * Extensible via TypeScript declaration merging:
1668
+ *
1669
+ * ```typescript
1670
+ * declare module '@camstack/types' {
1671
+ * interface CapabilityProviderMap {
1672
+ * 'my-custom-capability': IMyProvider
1673
+ * }
1674
+ * }
1675
+ * ```
1676
+ */
1677
+ interface CapabilityProviderMap {
1678
+ 'storage': IStorageProvider;
1679
+ 'log-destination': ILogDestination;
1680
+ 'analysis-pipeline': IAnalysisAddon;
1681
+ 'restreamer': unknown;
1682
+ 'webrtc': unknown;
1683
+ 'stream-broker': unknown;
1684
+ 'addon-pages': IAddonPageProvider;
1685
+ 'admin-ui': IAdminUI;
1686
+ 'auth-provider': IAuthProvider;
1687
+ 'notification-output': INotificationOutput;
1688
+ 'addon-routes': IAddonRouteProvider;
1689
+ 'recording-engine': unknown;
1690
+ 'analysis-data-persistence': IAnalysisDataPersistence;
1691
+ 'device-provider': unknown;
1692
+ 'network-access': unknown;
1693
+ 'streaming-engine': unknown;
1694
+ [key: string]: unknown;
1695
+ }
1696
+
1697
+ /**
1698
+ * CamstackContext -- injected into every provider, device, and addon.
1699
+ *
1700
+ * Each element receives a context scoped to its identity:
1701
+ * - logger is pre-scoped: (Frigate)[FrontDoor] for a device, (Frigate) for a provider
1702
+ * - storage is namespaced: all reads/writes are relative to this element's ID
1703
+ * - eventBus is shared (system-wide) but the source is pre-filled
1704
+ * - config provides read/write access to this element's persisted configuration
1705
+ *
1706
+ * The `id` is an immutable progressive identifier assigned at creation time.
1707
+ * It never changes even if the element is renamed.
1708
+ */
1709
+ interface CamstackContext {
1710
+ /** Immutable progressive ID of this element (e.g., 'provider:frigate-1', 'device:frigate-1/front-door') */
1711
+ readonly id: string;
1712
+ /** Pre-scoped logger -- logs automatically include this element's scope chain */
1713
+ readonly logger: IScopedLogger;
1714
+ /** Event bus -- shared system-wide, use for emitting and subscribing to events */
1715
+ readonly eventBus: IEventBus;
1716
+ /** Storage scoped to this element -- all paths are relative to this element's namespace */
1717
+ readonly storage: IStorageLocation;
1718
+ /** Persisted configuration for this element -- read/write/subscribe to changes */
1719
+ readonly config: IElementConfig;
1720
+ }
1721
+ /**
1722
+ * Scoped configuration store for an element.
1723
+ * Values are persisted in the element's storage namespace.
1724
+ */
1725
+ interface IElementConfig {
1726
+ /** Get the full config object */
1727
+ getAll(): Record<string, unknown>;
1728
+ /** Get a single value by dot-path (e.g., 'mqtt.brokerUrl') */
1729
+ get<T = unknown>(key: string): T | undefined;
1730
+ /** Set a single value */
1731
+ set(key: string, value: unknown): Promise<void>;
1732
+ /** Set the entire config object (merge or replace) */
1733
+ setAll(config: Record<string, unknown>): Promise<void>;
1734
+ /** Subscribe to config changes -- callback receives the new full config */
1735
+ onChange(callback: (config: Record<string, unknown>) => void): () => void;
1736
+ }
1737
+
1738
+ /**
1739
+ * Progress update sent from a task handler back to the hub.
1740
+ */
1741
+ interface TaskProgress {
1742
+ readonly percent?: number;
1743
+ readonly message?: string;
1744
+ readonly data?: unknown;
1745
+ }
1746
+ /**
1747
+ * Context provided to a task handler when executing a task.
1748
+ */
1749
+ interface TaskContext {
1750
+ /** ID of the agent executing this task */
1751
+ readonly taskId: string;
1752
+ /** ID of the agent executing this task */
1753
+ readonly agentId: string;
1754
+ /** Pre-scoped logger for the task */
1755
+ readonly logger: IScopedLogger;
1756
+ /** Send progress updates back to hub */
1757
+ reportProgress(progress: TaskProgress): void;
1758
+ /** Check if the task has been cancelled */
1759
+ isCancelled(): boolean;
1760
+ }
1761
+ /**
1762
+ * Security level for a task handler. Dangerous tasks (e.g., terminal.exec)
1763
+ * require explicit admin approval per agent.
1764
+ */
1765
+ type TaskHandlerSecurity = 'safe' | 'dangerous';
1766
+ /**
1767
+ * Pluggable task handler interface. Any addon can register task handlers
1768
+ * that the hub can dispatch to agents.
1769
+ */
1770
+ interface ITaskHandler {
1771
+ /** Unique task type identifier (e.g., 'pipeline.decode', 'system.info') */
1772
+ readonly taskType: string;
1773
+ /** Human-readable description */
1774
+ readonly description: string;
1775
+ /** Security level — dangerous tasks need explicit admin approval */
1776
+ readonly security?: TaskHandlerSecurity;
1777
+ /** Execute the task and return a result */
1778
+ handle(payload: unknown, context: TaskContext): Promise<unknown>;
1779
+ /** Optional cleanup when a running task is cancelled */
1780
+ cancel?(): Promise<void>;
617
1781
  }
618
1782
 
1783
+ /** Storage interface for addon file operations */
1784
+ interface IAddonFileStorage {
1785
+ readFile(path: string): Promise<Buffer>;
1786
+ writeFile(path: string, data: Buffer): Promise<void>;
1787
+ deleteFile(path: string): Promise<void>;
1788
+ listFiles(prefix?: string): Promise<readonly string[]>;
1789
+ exists(path: string): Promise<boolean>;
1790
+ }
1791
+ /** Storage interface for addon structured data (DB) */
1792
+ interface IAddonStructuredStorage {
1793
+ query(collection: string, filter?: Record<string, unknown>): Promise<readonly unknown[]>;
1794
+ insert(collection: string, data: Record<string, unknown>): Promise<void>;
1795
+ update(collection: string, id: string, data: Record<string, unknown>): Promise<void>;
1796
+ delete(collection: string, id: string): Promise<void>;
1797
+ }
1798
+ /** Named file storage locations available to addons */
1799
+ type AddonFileLocationName = 'data' | 'media' | 'recordings' | 'models' | 'cache' | 'logs';
1800
+ /** Named structured storage locations available to addons */
1801
+ type AddonStructuredLocationName = 'data' | 'events' | 'config';
1802
+ /** Combined storage location name (union of file and structured) */
1803
+ type AddonStorageLocationName = AddonFileLocationName | AddonStructuredLocationName;
1804
+ /** Storage access for addons -- replaces raw AddonLocationPaths */
1805
+ interface AddonStorageLocations {
1806
+ /** Get file storage for a named location */
1807
+ getFiles(location: AddonFileLocationName): IAddonFileStorage;
1808
+ /** Get structured storage (DB) for a named location */
1809
+ getStructured(location: AddonStructuredLocationName): IAddonStructuredStorage;
1810
+ }
1811
+ type AddonCapability = 'storage' | 'backup' | 'log-destination' | 'device-exporter' | 'network-access' | 'turn-provider' | 'webrtc-provider' | 'remote-access' | 'recording-engine' | 'notification-output' | 'streaming-engine' | 'pipeline-consumer' | 'detection-engine' | 'object-detector' | 'motion-analyzer' | 'sub-detector' | 'face-detector' | 'plate-detector' | 'recognizer' | 'face-recognizer' | 'plate-recognizer' | 'audio-classifier' | 'device-provider' | 'stream-broker' | 'analysis-data-persistence' | 'analysis-pipeline';
619
1812
  interface RequiredStep {
620
1813
  readonly slot: PipelineSlot;
621
1814
  readonly outputClasses: readonly string[];
@@ -625,8 +1818,13 @@ interface AddonManifest {
625
1818
  readonly id: string;
626
1819
  readonly name: string;
627
1820
  readonly version: string;
628
- readonly description: string;
629
- readonly packageName: string;
1821
+ readonly description?: string;
1822
+ readonly packageName?: string;
1823
+ readonly capabilities?: readonly (AddonCapability | {
1824
+ name: string;
1825
+ mode: string;
1826
+ })[];
1827
+ readonly requiredFeatures?: readonly string[];
630
1828
  readonly slot?: PipelineSlot;
631
1829
  readonly inputClasses?: readonly string[];
632
1830
  readonly outputClasses?: readonly string[];
@@ -638,30 +1836,103 @@ interface AddonManifest {
638
1836
  interface AddonLocationPaths {
639
1837
  readonly data: string;
640
1838
  readonly media: string;
1839
+ readonly recordings: string;
641
1840
  readonly models: string;
642
1841
  readonly logs: string;
643
1842
  readonly cache: string;
644
1843
  }
1844
+ /**
1845
+ * Minimal model-management API exposed to addons via AddonContext.
1846
+ */
1847
+ interface IAddonModelManager {
1848
+ /** Download (if necessary) and return the local filesystem path for a model. */
1849
+ downloadModel(id: string): Promise<string>;
1850
+ /** Absolute path to the shared models directory. */
1851
+ getModelsDir(): string;
1852
+ /** Whether a model file is already present on disk. */
1853
+ isDownloaded(id: string): boolean;
1854
+ }
1855
+ /**
1856
+ * AddonContext -- injected into every addon at initialize().
1857
+ *
1858
+ * The context includes both the CamstackContext fields (id, logger, eventBus, storage, config)
1859
+ * and addon-specific extras (addonConfig, models, locationPaths, router, network).
1860
+ */
645
1861
  interface AddonContext {
1862
+ /** Immutable progressive ID */
1863
+ readonly id: string;
1864
+ /** Pre-scoped logger */
1865
+ readonly logger: IScopedLogger;
1866
+ /** System event bus */
1867
+ readonly eventBus: IEventBus;
1868
+ /** Scoped storage location */
1869
+ readonly storage: IStorageLocation;
1870
+ /** Persisted config store */
1871
+ readonly config: IElementConfig;
1872
+ /** Bootstrap configuration from server settings (read-only, from config.yaml) */
646
1873
  readonly addonConfig: Readonly<Record<string, unknown>>;
1874
+ /** Model management -- download default models or retrieve cached paths. */
1875
+ models?: IAddonModelManager;
1876
+ /** Resolved filesystem paths for all named storage locations. */
647
1877
  readonly locationPaths: AddonLocationPaths;
1878
+ /** HTTP router for registering addon routes */
648
1879
  readonly router?: IAddonRouter;
1880
+ /** Network access provider */
649
1881
  readonly network?: INetworkProvider;
650
- readonly logger?: IScopedLogger;
651
- readonly eventBus?: IEventBus;
652
- readonly storage?: IStorageProvider;
1882
+ /** Storage provider */
1883
+ readonly storageProvider?: IStorageProvider;
1884
+ /**
1885
+ * Register a task handler that can be dispatched by the hub.
1886
+ * Only effective in agent mode — in hub mode, handlers are stored but not dispatched.
1887
+ */
1888
+ registerTaskHandler?(handler: ITaskHandler): void;
653
1889
  }
654
1890
  interface ICamstackAddon {
655
- readonly id: string;
1891
+ readonly id?: string;
656
1892
  readonly manifest: AddonManifest;
657
1893
  initialize(context: AddonContext): Promise<void>;
658
1894
  shutdown(): Promise<void>;
659
1895
  getConfigSchema?(): ConfigUISchema;
1896
+ /**
1897
+ * Return the provider instance for a declared capability.
1898
+ * Called by CapabilityRegistry after initialize() succeeds.
1899
+ * Returns null if this addon does not provide the requested capability.
1900
+ */
1901
+ getCapabilityProvider?<K extends keyof CapabilityProviderMap>(name: K): CapabilityProviderMap[K] | null;
1902
+ }
1903
+ /** Declaration of a UI page provided by an addon */
1904
+ interface AddonPageDeclaration {
1905
+ /** Unique page ID (scoped to addon) */
1906
+ readonly id: string;
1907
+ /** Display label for sidebar */
1908
+ readonly label: string;
1909
+ /** Lucide icon name for sidebar */
1910
+ readonly icon: string;
1911
+ /** Route path (e.g., '/benchmark') */
1912
+ readonly path: string;
1913
+ /** Relative path to JS bundle within addon package */
1914
+ readonly bundle: string;
1915
+ /** Web component custom element tag name */
1916
+ readonly element: string;
1917
+ }
1918
+ /** Provider interface for addons that expose UI pages */
1919
+ interface IAddonPageProvider {
1920
+ readonly id: string;
1921
+ getPages(): readonly AddonPageDeclaration[];
1922
+ }
1923
+ /** Provider interface for the admin UI shell (singleton capability) */
1924
+ interface IAdminUI {
1925
+ getStaticDir(): string;
1926
+ getVersion(): string;
660
1927
  }
661
1928
  interface AddonDeclaration {
662
1929
  readonly id: string;
663
1930
  readonly entry: string;
664
1931
  readonly slot: PipelineSlot;
1932
+ /** Capabilities this addon provides. Declared in package.json camstack.addons[].capabilities */
1933
+ readonly capabilities?: readonly CapabilityDeclaration[];
1934
+ /** UI pages provided by this addon */
1935
+ readonly pages?: readonly AddonPageDeclaration[];
665
1936
  }
666
1937
  interface AddonPackageManifest {
667
1938
  readonly addons: readonly AddonDeclaration[];
@@ -733,24 +2004,6 @@ interface IKnownPlateRepository {
733
2004
  delete(plateId: string): Promise<void>;
734
2005
  }
735
2006
 
736
- interface ICameraAnalyticsProvider {
737
- getLiveState(deviceId: string): CameraLiveState | null;
738
- getTracks(deviceId: string, filter?: TrackFilter): TrackedObjectSummary[];
739
- getZoneState(deviceId: string, zoneId: string): ZoneLiveState | null;
740
- getZoneHistory(deviceId: string, zoneId: string, options: TimeRangeOptions): Promise<ZoneHistoryPoint[]>;
741
- getHeatmap(deviceId: string, options: HeatmapOptions): Promise<HeatmapData>;
742
- getTrackDetail(deviceId: string, trackId: string): TrackDetail | null;
743
- getCameraStatus(deviceId: string): PipelineStatus[];
744
- }
745
- interface IAnalysisAddon extends ICamstackAddon, ICameraAnalyticsProvider {
746
- processFrame(deviceId: string, frame: FrameInput): Promise<DetectionEvent[]>;
747
- setCameraPipeline(deviceId: string, config: PipelineConfig): void;
748
- setCameraZones(deviceId: string, zones: ZoneDefinition[]): void;
749
- setKnownFaces(faces: KnownFace[]): void;
750
- setKnownPlates(plates: KnownPlate[]): void;
751
- onLiveStateChange(deviceId: string, callback: (state: CameraLiveState) => void): () => void;
752
- }
753
-
754
2007
  interface PythonProbeResult {
755
2008
  readonly available: boolean;
756
2009
  readonly version?: string;
@@ -770,6 +2023,39 @@ interface IPythonEnvironment {
770
2023
  spawn(script: string, args: readonly string[]): ChildProcess;
771
2024
  }
772
2025
 
2026
+ /**
2027
+ * Abstract storage backend -- resolves subpaths to absolute filesystem paths.
2028
+ */
2029
+ interface IStorageBackend {
2030
+ /** Backend type identifier */
2031
+ readonly type: string;
2032
+ /** Base path of this backend */
2033
+ readonly basePath: string;
2034
+ /** Resolve a subpath to an absolute path */
2035
+ resolve(subpath: string): string;
2036
+ /** Also support location-based resolution */
2037
+ resolve(location: StorageLocationName, ...segments: string[]): string;
2038
+ /** Check if the backend path exists and is writable */
2039
+ isAvailable(location?: StorageLocationName): boolean;
2040
+ /** Ensure base directory exists (mkdir -p equivalent) */
2041
+ initialize(): Promise<void>;
2042
+ /** Get the absolute path for a named storage location */
2043
+ getLocationPath?(location: StorageLocationName): string;
2044
+ }
2045
+ /** @deprecated Use IStorageProvider from storage.ts instead */
2046
+ interface IStorageProviderLegacy {
2047
+ getLocation(name: string): string;
2048
+ setLocationPath?(name: StorageLocationName, absolutePath: string): void;
2049
+ }
2050
+ /** Configuration for a storage location */
2051
+ interface StorageLocationConfig {
2052
+ readonly name: string;
2053
+ readonly basePath: string;
2054
+ readonly backendType?: string;
2055
+ }
2056
+ /** Default storage location paths, relative to dataPath */
2057
+ declare const DEFAULT_LOCATION_SUBPATHS: Record<string, string>;
2058
+
773
2059
  interface IPipelineRunner {
774
2060
  run(frame: FrameInput, config?: PipelineConfig): Promise<PipelineResult>;
775
2061
  runSingle(addonId: string, input: FrameInput | CropInput, config?: Record<string, unknown>): Promise<StepResult>;
@@ -790,9 +2076,978 @@ interface IPipelineValidator {
790
2076
  validate(config: PipelineConfig): ValidationResult;
791
2077
  }
792
2078
 
2079
+ type DeviceType = 'camera' | 'doorbell' | 'sensor' | 'switch' | 'thermostat' | 'light' | 'generic';
2080
+ interface DeviceState {
2081
+ online: boolean;
2082
+ lastSeen?: number;
2083
+ error?: string;
2084
+ }
2085
+ interface DeviceMetadata {
2086
+ manufacturer?: string;
2087
+ model?: string;
2088
+ firmware?: string;
2089
+ hardwareId?: string;
2090
+ ip?: string;
2091
+ }
2092
+ interface DeviceEvent {
2093
+ deviceId: string;
2094
+ type: string;
2095
+ timestamp: number;
2096
+ data: Record<string, unknown>;
2097
+ }
2098
+ interface IDevice {
2099
+ /** Immutable progressive ID */
2100
+ readonly id: string;
2101
+ readonly name: string;
2102
+ readonly providerId: string;
2103
+ readonly type: DeviceType;
2104
+ readonly capabilities: DeviceCapabilityName[];
2105
+ /** The device's context -- logger, storage, eventBus scoped to this device */
2106
+ readonly ctx: CamstackContext;
2107
+ getCapability<T extends IDeviceCapability>(cap: DeviceCapabilityName): T | null;
2108
+ hasCapability(cap: DeviceCapabilityName): boolean;
2109
+ getState(): DeviceState;
2110
+ getMetadata(): DeviceMetadata;
2111
+ subscribeEvents?(callback: (event: DeviceEvent) => void): () => void;
2112
+ /** Optional: declare configurable settings UI for this device */
2113
+ getConfigSchema?(): ConfigUISchema;
2114
+ }
2115
+
2116
+ interface ProviderStatus {
2117
+ connected: boolean;
2118
+ error?: string;
2119
+ deviceCount: number;
2120
+ }
2121
+ interface DiscoveredDevice {
2122
+ externalId: string;
2123
+ name: string;
2124
+ type: DeviceType;
2125
+ capabilities: DeviceCapabilityName[];
2126
+ metadata: DeviceMetadata;
2127
+ }
2128
+ interface LiveEvent {
2129
+ type: string;
2130
+ camera: string;
2131
+ timestamp: number;
2132
+ data: Record<string, unknown>;
2133
+ }
2134
+ interface IDeviceProvider {
2135
+ /** Immutable progressive ID */
2136
+ readonly id: string;
2137
+ readonly type: string;
2138
+ readonly name: string;
2139
+ readonly discoveryMode: 'auto' | 'manual' | 'both';
2140
+ /** The provider's context -- logger, storage, eventBus scoped to this provider */
2141
+ readonly ctx: CamstackContext;
2142
+ start(): Promise<void>;
2143
+ stop(): Promise<void>;
2144
+ getStatus(): ProviderStatus;
2145
+ discoverDevices(): Promise<DiscoveredDevice[]>;
2146
+ getDeviceConfigSchema?(): unknown;
2147
+ createDevice?(config: Record<string, unknown>): Promise<IDevice>;
2148
+ adoptDevice?(externalId: string, config?: Record<string, unknown>): Promise<IDevice>;
2149
+ getDevices(): IDevice[];
2150
+ subscribeLiveEvents(callback: (event: LiveEvent) => void): () => void;
2151
+ /** Optional: declare configurable settings UI for this provider */
2152
+ getConfigSchema?(): ConfigUISchema;
2153
+ }
2154
+
2155
+ interface StreamOption {
2156
+ id: string;
2157
+ label: string;
2158
+ protocol: 'rtsp' | 'http' | 'webrtc';
2159
+ quality: 'main' | 'mid' | 'sub' | 'mobile';
2160
+ url?: string;
2161
+ }
2162
+ type ConnectionMode = 'always-on' | 'on-demand';
2163
+ interface ICamera extends IDeviceCapability {
2164
+ kind: 'camera';
2165
+ getSnapshot(options?: {
2166
+ width?: number;
2167
+ height?: number;
2168
+ }): Promise<Buffer>;
2169
+ getStreamOptions(): Promise<StreamOption[]>;
2170
+ getStreamUrl(option: StreamOption): Promise<string>;
2171
+ onStreamRequested?(streamId: string): Promise<void>;
2172
+ getConnectionMode(): ConnectionMode;
2173
+ setConnectionMode(mode: ConnectionMode): Promise<void>;
2174
+ }
2175
+
2176
+ interface PtzPreset {
2177
+ id: string;
2178
+ name: string;
2179
+ }
2180
+ interface IPanTiltZoom extends IDeviceCapability {
2181
+ kind: 'panTiltZoom';
2182
+ move(command: {
2183
+ pan?: number;
2184
+ tilt?: number;
2185
+ zoom?: number;
2186
+ speed?: number;
2187
+ }): Promise<void>;
2188
+ continuousMove(command: {
2189
+ pan?: number;
2190
+ tilt?: number;
2191
+ zoom?: number;
2192
+ }): Promise<void>;
2193
+ stop(): Promise<void>;
2194
+ getPresets(): Promise<PtzPreset[]>;
2195
+ goToPreset(presetId: string): Promise<void>;
2196
+ goHome(): Promise<void>;
2197
+ getPosition(): Promise<{
2198
+ pan: number;
2199
+ tilt: number;
2200
+ zoom: number;
2201
+ }>;
2202
+ }
2203
+
2204
+ interface IMotionSensor extends IDeviceCapability {
2205
+ kind: 'motionSensor';
2206
+ isMotionDetected(): boolean;
2207
+ subscribe(callback: (active: boolean) => void): () => void;
2208
+ }
2209
+
2210
+ interface AudioEvent {
2211
+ timestamp: number;
2212
+ metric: string;
2213
+ value?: number;
2214
+ }
2215
+ interface IAudioDetector extends IDeviceCapability {
2216
+ kind: 'audioDetector';
2217
+ getAudioLevel(): number;
2218
+ subscribe(callback: (event: AudioEvent) => void): () => void;
2219
+ }
2220
+
2221
+ interface ITwoWayAudio extends IDeviceCapability {
2222
+ kind: 'twoWayAudio';
2223
+ startMicrophone(stream: unknown): Promise<void>;
2224
+ stopMicrophone(): Promise<void>;
2225
+ isMicrophoneActive(): boolean;
2226
+ }
2227
+
2228
+ interface DoorbellEvent {
2229
+ timestamp: number;
2230
+ deviceId: string;
2231
+ }
2232
+ interface IDoorbell extends IDeviceCapability {
2233
+ kind: 'doorbell';
2234
+ onRing(callback: (event: DoorbellEvent) => void): () => void;
2235
+ }
2236
+
2237
+ /** A stored event from a device */
2238
+ interface DeviceStoredEvent {
2239
+ id: string;
2240
+ type: string;
2241
+ timestamp: number;
2242
+ endTimestamp?: number;
2243
+ label?: string;
2244
+ score?: number;
2245
+ hasClip?: boolean;
2246
+ hasSnapshot?: boolean;
2247
+ thumbnailUrl?: string;
2248
+ data?: Record<string, unknown>;
2249
+ }
2250
+ /** Query filter for events */
2251
+ interface EventQuery {
2252
+ since?: number;
2253
+ until?: number;
2254
+ types?: string[];
2255
+ detectionClasses?: string[];
2256
+ clusterSpan?: number;
2257
+ onlyKeyEvents?: boolean;
2258
+ includeClip?: boolean;
2259
+ limit?: number;
2260
+ offset?: number;
2261
+ }
2262
+ /** Query result */
2263
+ interface EventQueryResult {
2264
+ events: DeviceStoredEvent[];
2265
+ total: number;
2266
+ }
2267
+ /** A cluster of nearby events */
2268
+ interface EventCluster {
2269
+ representative: DeviceStoredEvent;
2270
+ events: DeviceStoredEvent[];
2271
+ startMs: number;
2272
+ endMs: number;
2273
+ classes: string[];
2274
+ }
2275
+ interface IEvents extends IDeviceCapability {
2276
+ kind: 'events';
2277
+ getEvents(query: EventQuery): Promise<EventQueryResult>;
2278
+ getEventThumbnail(eventId: string): Promise<Buffer | null>;
2279
+ getEventSnapshot(eventId: string): Promise<Buffer | null>;
2280
+ getEventClipUrl(eventId: string): Promise<string | null>;
2281
+ }
2282
+
2283
+ interface TimeRange {
2284
+ since: number;
2285
+ until: number;
2286
+ }
2287
+ interface RecordingSegment {
2288
+ id: string;
2289
+ startTime: number;
2290
+ endTime: number;
2291
+ duration: number;
2292
+ }
2293
+ interface IRecording extends IDeviceCapability {
2294
+ kind: 'recording';
2295
+ getSegments(range: TimeRange): Promise<RecordingSegment[]>;
2296
+ getPlaybackUrl(startTime: number, endTime: number): Promise<string>;
2297
+ getThumbnailAt(timestampMs: number): Promise<Buffer | null>;
2298
+ }
2299
+
2300
+ interface AccessoryInfo {
2301
+ id: string;
2302
+ name: string;
2303
+ type: string;
2304
+ state?: Record<string, unknown>;
2305
+ }
2306
+ interface IAccessory extends IDeviceCapability {
2307
+ kind: 'accessory';
2308
+ getAccessories(): Promise<AccessoryInfo[]>;
2309
+ executeCommand(accessoryId: string, command: string, params?: Record<string, unknown>): Promise<void>;
2310
+ getAccessoryState(accessoryId: string): Promise<Record<string, unknown>>;
2311
+ }
2312
+
2313
+ interface IStatusLight extends IDeviceCapability {
2314
+ kind: 'statusLight';
2315
+ isOn(): boolean;
2316
+ setOn(on: boolean): Promise<void>;
2317
+ }
2318
+
2319
+ interface ISiren extends IDeviceCapability {
2320
+ kind: 'siren';
2321
+ isActive(): boolean;
2322
+ activate(durationMs?: number): Promise<void>;
2323
+ deactivate(): Promise<void>;
2324
+ }
2325
+
2326
+ interface ISwitch extends IDeviceCapability {
2327
+ kind: 'switch';
2328
+ isOn(): boolean;
2329
+ setOn(on: boolean): Promise<void>;
2330
+ toggle(): Promise<void>;
2331
+ }
2332
+
2333
+ type StreamFormat = 'webrtc' | 'hls' | 'mjpeg' | 'rtsp';
2334
+ interface StreamingSource {
2335
+ readonly url: string;
2336
+ readonly protocol: 'rtsp' | 'http' | 'rtmp';
2337
+ readonly deviceId: string;
2338
+ readonly providerId: string;
2339
+ readonly label?: string;
2340
+ }
2341
+ interface StreamInfo {
2342
+ readonly streamId: string;
2343
+ readonly source: StreamingSource;
2344
+ readonly formats: StreamFormat[];
2345
+ }
2346
+ interface StreamStatus {
2347
+ readonly active: boolean;
2348
+ readonly viewers: number;
2349
+ readonly startedAt?: number;
2350
+ }
2351
+ interface IStreamingEngine {
2352
+ initialize(config: Record<string, unknown>): Promise<void>;
2353
+ shutdown(): Promise<void>;
2354
+ registerStream(streamId: string, source: StreamingSource): Promise<void>;
2355
+ unregisterStream(streamId: string): Promise<void>;
2356
+ getStreamUrl(streamId: string, format: StreamFormat): string | null;
2357
+ proxyWhepOffer(streamId: string, sdpOffer: string): Promise<string>;
2358
+ listStreams(): StreamInfo[];
2359
+ getStreamStatus(streamId: string): StreamStatus | null;
2360
+ }
2361
+
2362
+ type UserRole = 'super_admin' | 'admin' | 'viewer';
2363
+ interface UserPermissions {
2364
+ role: UserRole;
2365
+ allowedProviders: string[] | '*';
2366
+ allowedDevices: Record<string, string[] | '*'>;
2367
+ }
2368
+ interface UserRecord {
2369
+ id: string;
2370
+ username: string;
2371
+ passwordHash: string;
2372
+ role: UserRole;
2373
+ allowedProviders: string[] | '*';
2374
+ allowedDevices: Record<string, string[] | '*'>;
2375
+ createdAt: number;
2376
+ updatedAt: number;
2377
+ }
2378
+ interface ApiKeyRecord {
2379
+ id: string;
2380
+ label: string;
2381
+ role: UserRole;
2382
+ allowedProviders: string[] | '*';
2383
+ allowedDevices: Record<string, string[] | '*'>;
2384
+ tokenHash: string;
2385
+ tokenPrefix: string;
2386
+ createdAt: number;
2387
+ lastUsedAt?: number;
2388
+ }
2389
+ interface TokenPayload {
2390
+ type?: 'api_key';
2391
+ keyId?: string;
2392
+ userId?: string;
2393
+ username?: string;
2394
+ role: UserRole;
2395
+ allowedProviders: string[] | '*';
2396
+ allowedDevices: Record<string, string[] | '*'>;
2397
+ iat?: number;
2398
+ exp?: number;
2399
+ }
2400
+ interface AuthenticatedUser {
2401
+ id: string;
2402
+ username: string;
2403
+ role: UserRole;
2404
+ permissions: UserPermissions;
2405
+ isApiKey: boolean;
2406
+ }
2407
+
2408
+ type ElementState = 'stopped' | 'starting' | 'running' | 'stopping' | 'error' | 'disabled';
2409
+ interface ElementStatus {
2410
+ state: ElementState;
2411
+ error?: string;
2412
+ startedAt?: number;
2413
+ stoppedAt?: number;
2414
+ restartCount: number;
2415
+ uptime: number;
2416
+ }
2417
+ interface ILifecycleManaged {
2418
+ readonly id: string;
2419
+ getStatus(): ElementStatus;
2420
+ start(): Promise<void>;
2421
+ stop(): Promise<void>;
2422
+ restart(): Promise<void>;
2423
+ enable(): Promise<void>;
2424
+ disable(): Promise<void>;
2425
+ }
2426
+
2427
+ /**
2428
+ * Server-side network access interfaces.
2429
+ * These cover tunnel/VPN/endpoint management, NOT the addon-facing INetworkProvider.
2430
+ */
2431
+ interface INetworkEndpoint {
2432
+ id: string;
2433
+ type: 'lan' | 'mdns' | 'domain' | 'tunnel' | 'vpn' | 'custom';
2434
+ provider: string;
2435
+ url: string;
2436
+ internal: boolean;
2437
+ capabilities: EndpointCapabilities;
2438
+ priority: number;
2439
+ status: 'online' | 'offline' | 'unknown';
2440
+ metadata?: Record<string, unknown>;
2441
+ }
2442
+ interface EndpointCapabilities {
2443
+ supportsWebRTC: boolean;
2444
+ supportsWebSocket: boolean;
2445
+ supportsSSE: boolean;
2446
+ maxBandwidthMbps?: number;
2447
+ }
2448
+ interface INetworkAccessProvider {
2449
+ id: string;
2450
+ type: string;
2451
+ start(): Promise<INetworkEndpoint>;
2452
+ stop(): Promise<void>;
2453
+ getEndpoint(): INetworkEndpoint | null;
2454
+ getStatus(): NetworkAccessStatus;
2455
+ }
2456
+ interface NetworkAccessStatus {
2457
+ connected: boolean;
2458
+ publicUrl?: string;
2459
+ error?: string;
2460
+ connectedSince?: number;
2461
+ }
2462
+ interface IExposedResource {
2463
+ id: string;
2464
+ providerId: string;
2465
+ deviceId?: string;
2466
+ type: ExposedResourceType;
2467
+ path: string;
2468
+ auth: ResourceAuthMode;
2469
+ description: string;
2470
+ contentType?: string;
2471
+ enabled: boolean;
2472
+ }
2473
+ type ExposedResourceType = 'snapshot' | 'stream' | 'webhook' | 'api' | 'media' | 'custom';
2474
+ type ResourceAuthMode = {
2475
+ mode: 'public';
2476
+ } | {
2477
+ mode: 'token';
2478
+ token: string;
2479
+ } | {
2480
+ mode: 'api-key';
2481
+ } | {
2482
+ mode: 'jwt';
2483
+ } | {
2484
+ mode: 'ip-whitelist';
2485
+ ips: string[];
2486
+ };
2487
+ interface IResourceExposer {
2488
+ getExposedResources(): IExposedResource[];
2489
+ handleResourceRequest(resourceId: string, request: ExposedResourceRequest): Promise<ExposedResourceResponse>;
2490
+ }
2491
+ interface ExposedResourceRequest {
2492
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE';
2493
+ path: string;
2494
+ headers: Record<string, string>;
2495
+ query: Record<string, string>;
2496
+ body?: Buffer | string;
2497
+ remoteIp: string;
2498
+ }
2499
+ interface ExposedResourceResponse {
2500
+ status: number;
2501
+ headers?: Record<string, string>;
2502
+ body?: Buffer | string | NodeJS.ReadableStream;
2503
+ }
2504
+
2505
+ interface ProcessStats {
2506
+ pid: number;
2507
+ cpu: number;
2508
+ memory: number;
2509
+ uptime: number;
2510
+ restartCount: number;
2511
+ }
2512
+ interface ProcessConfig {
2513
+ id: string;
2514
+ label: string;
2515
+ command?: string;
2516
+ modulePath?: string;
2517
+ args?: string[];
2518
+ env?: Record<string, string>;
2519
+ autoRestart: boolean;
2520
+ maxRestarts?: number;
2521
+ healthCheck?: {
2522
+ intervalMs: number;
2523
+ timeoutMs: number;
2524
+ failureThreshold: number;
2525
+ };
2526
+ }
2527
+ interface ManagedProcessStatus {
2528
+ id: string;
2529
+ label: string;
2530
+ state: ElementState;
2531
+ pid?: number;
2532
+ stats?: ProcessStats;
2533
+ lastCrashAt?: number;
2534
+ lastCrashError?: string;
2535
+ restartCount: number;
2536
+ nextRestartAt?: number;
2537
+ }
2538
+ /** Minimal interface for ProcessManagerService used by addons */
2539
+ interface IProcessManager {
2540
+ register(config: ProcessConfig): unknown;
2541
+ start(id: string): Promise<void>;
2542
+ stop(id: string): Promise<void>;
2543
+ }
2544
+
2545
+ interface FeatureManifest {
2546
+ streaming: boolean;
2547
+ notifications: boolean;
2548
+ objectDetection: boolean;
2549
+ remoteAccess: boolean;
2550
+ agentCluster: boolean;
2551
+ smartHome: boolean;
2552
+ recordings: boolean;
2553
+ backup: boolean;
2554
+ repl: boolean;
2555
+ }
2556
+ type FeatureFlag = keyof FeatureManifest;
2557
+ declare const DEFAULT_FEATURES: FeatureManifest;
2558
+
2559
+ interface AgentInfo {
2560
+ readonly id: string;
2561
+ readonly name: string;
2562
+ readonly capabilities: readonly string[];
2563
+ readonly host: string;
2564
+ readonly port: number;
2565
+ readonly resources?: AgentResources;
2566
+ }
2567
+ interface AgentResources {
2568
+ readonly cpuCores?: number;
2569
+ readonly memoryMB?: number;
2570
+ readonly gpuAvailable?: boolean;
2571
+ readonly gpuModel?: string;
2572
+ }
2573
+ interface AgentStatus {
2574
+ readonly id: string;
2575
+ readonly name: string;
2576
+ readonly state: 'online' | 'offline' | 'degraded';
2577
+ readonly capabilities: readonly string[];
2578
+ readonly lastHeartbeat: number;
2579
+ readonly connectedSince?: number;
2580
+ readonly resources?: AgentResources;
2581
+ readonly activeTaskCount: number;
2582
+ readonly completedTaskCount: number;
2583
+ readonly failedTaskCount: number;
2584
+ }
2585
+ interface AgentTask {
2586
+ readonly id: string;
2587
+ readonly capability: string;
2588
+ readonly input: Record<string, unknown>;
2589
+ readonly timeout: number;
2590
+ readonly priority: 'low' | 'normal' | 'high';
2591
+ readonly label?: string;
2592
+ }
2593
+ interface AgentTaskResult {
2594
+ readonly taskId: string;
2595
+ readonly agentId: string;
2596
+ readonly status: 'success' | 'error' | 'timeout';
2597
+ readonly output?: Record<string, unknown>;
2598
+ readonly error?: string;
2599
+ readonly durationMs: number;
2600
+ }
2601
+ interface TaskDispatchOptions {
2602
+ readonly preferredAgent?: string;
2603
+ readonly capability: string;
2604
+ readonly remoteOnly?: boolean;
2605
+ }
2606
+ interface AgentEntry {
2607
+ readonly info: AgentInfo;
2608
+ state: 'online' | 'offline' | 'degraded';
2609
+ connectedSince: number;
2610
+ lastHeartbeat: number;
2611
+ activeTaskCount: number;
2612
+ completedTaskCount: number;
2613
+ failedTaskCount: number;
2614
+ }
2615
+
2616
+ type AgentCapability = 'decoder' | 'transcoder' | 'detector' | 'recorder';
2617
+ type RolePriority = 'primary' | 'backup' | 'overflow';
2618
+ interface CameraRoleAssignment {
2619
+ cameraId: string;
2620
+ role: AgentCapability;
2621
+ agentId: string;
2622
+ priority: RolePriority;
2623
+ rtspUrl?: string;
2624
+ detectionConfig?: {
2625
+ modelId: string;
2626
+ runtime: string;
2627
+ confidence: number;
2628
+ fps: number;
2629
+ classes?: string[];
2630
+ };
2631
+ }
2632
+ type HubToAgentMessage = {
2633
+ type: 'assign';
2634
+ assignment: CameraRoleAssignment;
2635
+ } | {
2636
+ type: 'unassign';
2637
+ cameraId: string;
2638
+ role: AgentCapability;
2639
+ } | {
2640
+ type: 'recording.start';
2641
+ cameraId: string;
2642
+ rtspUrl: string;
2643
+ } | {
2644
+ type: 'recording.stop';
2645
+ cameraId: string;
2646
+ } | {
2647
+ type: 'benchmark.run';
2648
+ config: RemoteBenchmarkConfig;
2649
+ } | {
2650
+ type: 'config.update';
2651
+ addonId: string;
2652
+ config: Record<string, unknown>;
2653
+ } | {
2654
+ type: 'task.execute';
2655
+ taskId: string;
2656
+ taskType: string;
2657
+ payload: unknown;
2658
+ } | {
2659
+ type: 'task.cancel';
2660
+ taskId: string;
2661
+ } | {
2662
+ type: 'addon.install';
2663
+ package: string;
2664
+ version?: string;
2665
+ } | {
2666
+ type: 'addon.uninstall';
2667
+ package: string;
2668
+ } | {
2669
+ type: 'addon.update';
2670
+ package: string;
2671
+ version?: string;
2672
+ } | {
2673
+ type: 'addon.restart';
2674
+ } | {
2675
+ type: 'ping';
2676
+ };
2677
+ type AgentToHubMessage = {
2678
+ type: 'register';
2679
+ info: AgentRegistrationInfo;
2680
+ } | {
2681
+ type: 'heartbeat';
2682
+ status: AgentRuntimeStatus;
2683
+ } | {
2684
+ type: 'detection.result';
2685
+ cameraId: string;
2686
+ detections: DetectionResult[];
2687
+ timestamp: number;
2688
+ inferenceMs: number;
2689
+ } | {
2690
+ type: 'recording.segment';
2691
+ cameraId: string;
2692
+ segment: RecordingSegmentInfo;
2693
+ } | {
2694
+ type: 'recording.status';
2695
+ cameraId: string;
2696
+ recording: boolean;
2697
+ } | {
2698
+ type: 'benchmark.progress';
2699
+ event: BenchmarkStreamEvent;
2700
+ } | {
2701
+ type: 'benchmark.result';
2702
+ report: BenchmarkReport;
2703
+ } | {
2704
+ type: 'task.result';
2705
+ taskId: string;
2706
+ success: boolean;
2707
+ result?: unknown;
2708
+ error?: string;
2709
+ } | {
2710
+ type: 'task.progress';
2711
+ taskId: string;
2712
+ progress: TaskProgress;
2713
+ } | {
2714
+ type: 'addon.install-result';
2715
+ package: string;
2716
+ success: boolean;
2717
+ version?: string;
2718
+ error?: string;
2719
+ } | {
2720
+ type: 'addon.uninstall-result';
2721
+ package: string;
2722
+ success: boolean;
2723
+ error?: string;
2724
+ } | {
2725
+ type: 'addon.update-result';
2726
+ package: string;
2727
+ success: boolean;
2728
+ version?: string;
2729
+ error?: string;
2730
+ } | {
2731
+ type: 'log';
2732
+ level: string;
2733
+ scope: string;
2734
+ message: string;
2735
+ } | {
2736
+ type: 'pong';
2737
+ };
2738
+ declare const BINARY_FRAME_HEADER_SIZE = 29;
2739
+ declare const BINARY_FRAME_TYPE = 1;
2740
+ interface AgentRegistrationInfo {
2741
+ id: string;
2742
+ name: string;
2743
+ capabilities: AgentCapability[];
2744
+ host: string;
2745
+ port: number;
2746
+ platform: string;
2747
+ arch: string;
2748
+ cpuCores: number;
2749
+ memoryMB: number;
2750
+ gpuModel?: string;
2751
+ pythonRuntimes: string[];
2752
+ httpPort: number;
2753
+ /** Task types this agent can handle (e.g., 'pipeline.decode', 'system.info') */
2754
+ taskTypes?: string[];
2755
+ /** Installed addons on this agent */
2756
+ installedAddons?: string[];
2757
+ }
2758
+ interface AgentRuntimeStatus {
2759
+ activeCameras: number;
2760
+ cpuPercent: number;
2761
+ memoryPercent: number;
2762
+ fps: Record<string, number>;
2763
+ errors: string[];
2764
+ }
2765
+ interface DetectionResult {
2766
+ className: string;
2767
+ score: number;
2768
+ bbox: [number, number, number, number];
2769
+ }
2770
+ interface RecordingSegmentInfo {
2771
+ id: string;
2772
+ startTime: number;
2773
+ endTime: number;
2774
+ duration: number;
2775
+ sizeBytes: number;
2776
+ path: string;
2777
+ format: 'mp4' | 'ts';
2778
+ }
2779
+ interface RemoteBenchmarkConfig {
2780
+ runtime: string;
2781
+ modelId?: string;
2782
+ durationMs: number;
2783
+ warmupMs: number;
2784
+ inputWidth: number;
2785
+ inputHeight: number;
2786
+ }
2787
+ interface BenchmarkStreamEvent {
2788
+ phase: 'warmup' | 'running';
2789
+ iteration: number;
2790
+ inferenceMs: number;
2791
+ }
2792
+ interface BenchmarkReport {
2793
+ runtime: string;
2794
+ modelId?: string;
2795
+ iterations: number;
2796
+ meanMs: number;
2797
+ p50Ms: number;
2798
+ p95Ms: number;
2799
+ p99Ms: number;
2800
+ minMs: number;
2801
+ maxMs: number;
2802
+ }
2803
+
2804
+ type Unsubscribe = () => void;
2805
+ interface StreamSource {
2806
+ readonly type: string;
2807
+ readonly url: string;
2808
+ readonly videoCodec?: string;
2809
+ readonly audioCodec?: string;
2810
+ readonly metadata?: Readonly<Record<string, unknown>>;
2811
+ }
2812
+ interface StreamSourceOption {
2813
+ readonly id: string;
2814
+ readonly label: string;
2815
+ readonly source: StreamSource;
2816
+ readonly resolution?: {
2817
+ readonly width: number;
2818
+ readonly height: number;
2819
+ };
2820
+ readonly primary?: boolean;
2821
+ }
2822
+ interface EncodedPacket {
2823
+ readonly type: 'video' | 'audio';
2824
+ readonly data: Buffer;
2825
+ readonly pts: number;
2826
+ readonly dts: number;
2827
+ readonly keyframe: boolean;
2828
+ readonly codec: string;
2829
+ }
2830
+ interface DecodedFrame {
2831
+ readonly data: Buffer;
2832
+ readonly width: number;
2833
+ readonly height: number;
2834
+ readonly format: 'jpeg' | 'rgb' | 'yuv420';
2835
+ readonly timestamp: number;
2836
+ }
2837
+ interface DecodedAudioChunk {
2838
+ readonly data: Buffer;
2839
+ readonly sampleRate: number;
2840
+ readonly channels: number;
2841
+ readonly timestamp: number;
2842
+ }
2843
+ interface DecodeOptions {
2844
+ readonly maxFps?: number;
2845
+ readonly format?: 'jpeg' | 'rgb' | 'yuv420';
2846
+ readonly scale?: number;
2847
+ }
2848
+ type BrokerStatus = 'idle' | 'connecting' | 'streaming' | 'error' | 'stopped';
2849
+ interface BrokerStats {
2850
+ readonly status: BrokerStatus;
2851
+ readonly inputFps: number;
2852
+ readonly decodeFps: number;
2853
+ readonly encodedSubscribers: number;
2854
+ readonly decodedSubscribers: number;
2855
+ readonly uptimeMs: number;
2856
+ }
2857
+ interface IStreamBroker {
2858
+ readonly deviceId: string;
2859
+ readonly status: BrokerStatus;
2860
+ start(source: StreamSource): Promise<void>;
2861
+ stop(): Promise<void>;
2862
+ onEncodedData(callback: (packet: EncodedPacket) => void): Unsubscribe;
2863
+ onDecodedFrame(callback: (frame: DecodedFrame) => void, options?: DecodeOptions): Unsubscribe;
2864
+ onDecodedAudioChunk(callback: (chunk: DecodedAudioChunk) => void): Unsubscribe;
2865
+ getStats(): BrokerStats;
2866
+ getLocalStreamUrl(): string;
2867
+ }
2868
+ /**
2869
+ * Manager for stream brokers — the server interacts with this interface,
2870
+ * while the concrete implementation lives in the addon-pipeline package.
2871
+ */
2872
+ interface IStreamBrokerManager {
2873
+ createBroker(brokerId: string, source: StreamSource): Promise<IStreamBroker>;
2874
+ getBroker(brokerId: string): IStreamBroker | undefined;
2875
+ destroyBroker(brokerId: string): Promise<void>;
2876
+ destroyBrokersForDevice(deviceId: string): Promise<void>;
2877
+ listBrokers(): readonly IStreamBroker[];
2878
+ destroyAll(): Promise<void>;
2879
+ setRestreamers(restreamers: readonly unknown[]): void;
2880
+ getRestreamers(): readonly unknown[];
2881
+ }
2882
+ type CameraPriority = 'always-on' | 'default';
2883
+ type CameraPhase = 'watching' | 'active';
2884
+ interface CameraDetectionConfig {
2885
+ readonly enabled: boolean;
2886
+ readonly priority: CameraPriority;
2887
+ readonly pipeline: PipelineConfig;
2888
+ readonly motionStreamId: string;
2889
+ readonly detectionStreamId: string;
2890
+ readonly motionFps: number;
2891
+ readonly detectionFps: number;
2892
+ readonly motionCooldownMs: number;
2893
+ readonly audioStreamId?: string;
2894
+ }
2895
+
2896
+ interface RegisteredStream {
2897
+ readonly streamId: string;
2898
+ readonly label: string;
2899
+ readonly codec: string;
2900
+ readonly type: 'video' | 'audio';
2901
+ readonly sourceUrl?: string;
2902
+ }
2903
+ interface RestreamerExposedResource {
2904
+ readonly type: string;
2905
+ readonly format: string;
2906
+ readonly value: string;
2907
+ readonly metadata?: Readonly<Record<string, unknown>>;
2908
+ }
2909
+ interface IRestreamer {
2910
+ readonly id: string;
2911
+ readonly name: string;
2912
+ registerDevice(deviceId: string, streams: readonly RegisteredStream[]): Promise<void>;
2913
+ pushPacket(streamId: string, packet: EncodedPacket): void;
2914
+ unregisterDevice(deviceId: string): Promise<void>;
2915
+ getExposedResources(deviceId: string): readonly RestreamerExposedResource[];
2916
+ proxyWhepOffer?(streamId: string, sdpOffer: string): Promise<string>;
2917
+ }
2918
+
2919
+ interface DecoderSessionConfig {
2920
+ readonly codec: string;
2921
+ readonly maxFps: number;
2922
+ readonly outputFormat: 'jpeg' | 'rgb' | 'yuv420';
2923
+ readonly scale: number;
2924
+ }
2925
+ interface DecoderStats {
2926
+ readonly inputFps: number;
2927
+ readonly outputFps: number;
2928
+ readonly avgDecodeTimeMs: number;
2929
+ readonly droppedFrames: number;
2930
+ }
2931
+ interface IDecoderSession {
2932
+ pushPacket(packet: EncodedPacket): void;
2933
+ onFrame(callback: (frame: DecodedFrame) => void): Unsubscribe;
2934
+ updateConfig(config: Partial<DecoderSessionConfig>): void;
2935
+ destroy(): Promise<void>;
2936
+ getStats(): DecoderStats;
2937
+ }
2938
+ interface IDecoderProvider {
2939
+ readonly id: string;
2940
+ readonly name: string;
2941
+ supportsCodec(codec: string): boolean;
2942
+ createSession(config: DecoderSessionConfig): IDecoderSession;
2943
+ }
2944
+
2945
+ interface FfmpegConfig {
2946
+ readonly path: string;
2947
+ readonly hwaccel?: 'vaapi' | 'qsv' | 'videotoolbox' | 'cuda' | 'v4l2m2m' | 'none';
2948
+ readonly inputArgs?: readonly string[];
2949
+ readonly outputArgs?: readonly string[];
2950
+ readonly videoCodec?: string;
2951
+ readonly audioCodec?: string;
2952
+ readonly threads?: number;
2953
+ }
2954
+
2955
+ type ModelType = 'detection' | 'face-detection' | 'face-recognition' | 'plate-detection' | 'ocr' | 'embedding' | 'audio-classification' | 'custom';
2956
+ type ModelFormat = 'onnx' | 'tflite' | 'coreml' | 'openvino';
2957
+ interface ServerModelCatalogEntry {
2958
+ readonly id: string;
2959
+ readonly name: string;
2960
+ readonly description: string;
2961
+ readonly type: ModelType;
2962
+ readonly format: ModelFormat;
2963
+ readonly available: boolean;
2964
+ readonly downloadUrl?: string;
2965
+ readonly fallbackDownloadUrls?: readonly string[];
2966
+ readonly sizeMB?: number;
2967
+ readonly exportHint?: string;
2968
+ readonly inputSize: {
2969
+ readonly width: number;
2970
+ readonly height: number;
2971
+ readonly channels?: number;
2972
+ };
2973
+ readonly labels?: readonly string[];
2974
+ }
2975
+ interface IModelCatalogProvider {
2976
+ readonly pluginId: string;
2977
+ getModelCatalog(): ServerModelCatalogEntry[];
2978
+ }
2979
+
2980
+ interface StreamNetworkStats {
2981
+ readonly nominalBitrateKbps: number;
2982
+ readonly observedBitrateKbps: number;
2983
+ readonly peakBitrateKbps: number;
2984
+ readonly packetLossPercent: number;
2985
+ readonly lastUpdated: number;
2986
+ }
2987
+ interface ClientNetworkStats {
2988
+ readonly rttMs: number;
2989
+ readonly jitterMs: number;
2990
+ readonly estimatedBandwidthKbps: number;
2991
+ readonly lastUpdated: number;
2992
+ }
2993
+ interface DeviceNetworkStats {
2994
+ readonly deviceId: string;
2995
+ readonly streams: Readonly<Record<string, StreamNetworkStats>>;
2996
+ readonly client?: ClientNetworkStats;
2997
+ }
2998
+ interface INetworkQualityTracker {
2999
+ reportStreamStats(deviceId: string, streamId: string, bitrateKbps: number, packetLoss?: number): void;
3000
+ reportClientStats(deviceId: string, stats: Omit<ClientNetworkStats, 'lastUpdated'>): void;
3001
+ getDeviceStats(deviceId: string): DeviceNetworkStats | null;
3002
+ getAllStats(): readonly DeviceNetworkStats[];
3003
+ }
3004
+
3005
+ interface IWebRtcProvider {
3006
+ readonly id: string;
3007
+ readonly name: string;
3008
+ handleOffer(streamId: string, sdpOffer: string): Promise<string>;
3009
+ supportsStream(streamId: string): boolean;
3010
+ registerStream(streamId: string, codec: string): Promise<void>;
3011
+ pushPacket(streamId: string, packet: EncodedPacket): void;
3012
+ unregisterStream(streamId: string): Promise<void>;
3013
+ }
3014
+
3015
+ interface ReplContext {
3016
+ scope: ReplScope;
3017
+ variables: Record<string, unknown>;
3018
+ }
3019
+ type ReplScope = {
3020
+ type: 'system';
3021
+ } | {
3022
+ type: 'provider';
3023
+ providerId: string;
3024
+ } | {
3025
+ type: 'device';
3026
+ deviceId: string;
3027
+ } | {
3028
+ type: 'addon';
3029
+ addonId: string;
3030
+ };
3031
+ interface ReplResult {
3032
+ output: string;
3033
+ type: 'value' | 'error' | 'void';
3034
+ duration: number;
3035
+ }
3036
+ interface IReplEngine {
3037
+ execute(code: string, context: ReplContext): Promise<ReplResult>;
3038
+ getCompletions(partial: string, context: ReplContext): Promise<string[]>;
3039
+ }
3040
+
793
3041
  declare const HF_REPO = "camstack/camstack-models";
794
3042
  declare const HF_BASE_URL = "https://huggingface.co/camstack/camstack-models/resolve/main";
795
3043
 
796
3044
  declare function hfModelUrl(repo: string, path: string): string;
797
3045
 
798
- export { type AccuracyStats, type AddonContext, type AddonDeclaration, type AddonLocationPaths, type AddonManifest, type AddonPackageManifest, type AgentBenchmarkStatus, type AudioChunkInput, type BenchmarkConfig, type BenchmarkExecution, type BenchmarkMode, type BenchmarkSource, type BenchmarkTarget, type BenchmarkTargetResult, type BoundingBox, type CameraAnalysisConfig, type CameraLiveState, ClassMapDefinition, type Classification, type ClassifierOutput, type ConfigBooleanField, type ConfigField, type ConfigFieldBase, type ConfigModelSelectorField, type ConfigNumberField, type ConfigSection, type ConfigSelectField, type ConfigSliderField, type ConfigTextField, type ConfigUISchema, type CropInput, type CropperOutput, CustomModelMetadata, type DetectionDevice, type DetectionEvent, type DetectionEventType, DetectionModel, type DetectionRuntime, type DetectorOutput, type EventCallback, type EventSnapshot, type FrameInput, type GroundTruth, type GroundTruthAnnotation, HF_BASE_URL, HF_REPO, type HeatmapData, type HeatmapOptions, type HttpMethod, type IAddonRouter, type IAnalysisAddon, type ICameraAnalyticsProvider, type ICamstackAddon, type IClassifierProvider, type ICropperProvider, type IDetectionAddon, type IDetectorProvider, type IEventBus, type IInferenceEngine, type IKnownFaceRepository, type IKnownPlateRepository, type ILogDestination, type INetworkProvider, type IPipelineRunner, type IPipelineSlotProvider, type IPipelineValidator, type IPythonEnvironment, type IRemoteAccessProvider, type IScopedLogger, type IStorageBackend, type IStorageProvider, type ITurnProvider, type IZoneRepository, type KnownAudioEvent, type KnownFace, type KnownPlate, LabelDefinition, type Landmark, type LatencyStats, type LogLevel, ModelCatalogEntry, ModelFormat, ModelOutputFormat, type MultiBenchmarkReport, type NetworkAddress, type ObjectState, type PipelineConfig, type PipelineNode, type PipelineResult, type PipelineSlot, type PipelineStatus, type ProbeCapability, type ProbeResult, type PublicAddress, type PythonEnvReady, type PythonProbeResult, type RemoteAccessStatus, type RequiredStep, type ResolveEngineOptions, type ResourceStats, type RouteHandler, type RouteRequest, type RouteResponse, type SpatialDetection, type StepError, type StepResult, type StorageLocationName, type SystemInfo, type TimeRangeOptions, type TrackDetail, type TrackFilter, type TrackedDetection, type TrackedObjectState, type TrackedObjectSummary, type TurnCredentials, type TurnServer, type ValidationIssue, type ValidationResult, type ZoneDefinition, type ZoneEvent, type ZoneHistoryPoint, type ZoneLiveState, hfModelUrl };
3046
+ /** Cosine similarity between two embedding vectors */
3047
+ declare function cosineSimilarity(a: Float32Array, b: Float32Array): number;
3048
+
3049
+ declare const COCO_80_LABELS: readonly LabelDefinition[];
3050
+ declare const MACRO_LABELS: readonly LabelDefinition[];
3051
+ declare const COCO_TO_MACRO: ClassMapDefinition;
3052
+
3053
+ export { type AccessoryInfo, type AccuracyStats, type ActiveDetection, type AddonCapability, type AddonContext, type AddonDeclaration, type AddonFileLocationName, type AddonHttpReply, type AddonHttpRequest, type AddonLocationPaths, type AddonManifest, type AddonPackageManifest, type AddonPageDeclaration, type AddonStorageLocationName, type AddonStorageLocations, type AddonStructuredLocationName, type AgentBenchmarkStatus, type AgentCapability, type AgentEntry, type AgentInfo, type AgentRegistrationInfo, type AgentResources, type AgentRuntimeStatus, type AgentStatus, type AgentTask, type AgentTaskResult, type AgentToHubMessage, type AnalysisContext, type AnalysisEvent, type AnnotatedSnapshotResult, type ApiKeyRecord, type AudioChunk, type AudioChunkCallback, type AudioChunkInput, type AudioClassification, type AudioEvent, type AuthResult, type AuthenticatedUser, BINARY_FRAME_HEADER_SIZE, BINARY_FRAME_TYPE, type BenchmarkConfig, type BenchmarkExecution, type BenchmarkMode, type BenchmarkReport, type BenchmarkSource, type BenchmarkStreamEvent, type BenchmarkTarget, type BenchmarkTargetResult, type BoundingBox, type BrokerStats, type BrokerStatus, COCO_80_LABELS, COCO_TO_MACRO, type CameraAnalysisConfig, type CameraDetectionConfig, type CameraLiveState, type CameraPhase, type CameraPriority, type CameraRoleAssignment, type CamstackContext, type CapabilityBinding, type CapabilityConsumerRegistration, type CapabilityDeclaration, type CapabilityInfo, type CapabilityMode, type CapabilityProviderMap, type ClassMapDefinition, type Classification, type ClassifierOutput, type ClientNetworkStats, type ClipRecognizer, type ConfigBooleanField, type ConfigColorField, type ConfigCondition, type ConfigField, type ConfigFieldBase, type ConfigGroupField, type ConfigInfoField, type ConfigModelSelectorField, type ConfigMultiSelectField, type ConfigNumberField, type ConfigOption, type ConfigPasswordField, type ConfigSection, type ConfigSelectField, type ConfigSeparatorField, type ConfigSliderField, type ConfigTagsField, type ConfigTextAreaField, type ConfigTextField, type ConfigUISchema, type ConnectionMode, type CropInput, type CropperOutput, type CustomModelMetadata, DEFAULT_FEATURES, DEFAULT_LOCATION_SUBPATHS, DEFAULT_RETENTION, DETECTION_TYPES, type DecodeOptions, type DecodedAudioChunk, type DecodedFrame, type DecoderSessionConfig, type DecoderStats, type Detection, type DetectionDevice, type DetectionEvent, type DetectionEventType, type DetectionFrame, type DetectionLine, type DetectionModel, type DetectionResult, type DetectionRuntime, type DetectionType, type DetectionZone, type DetectorOutput, type DeviceCapabilityBinding, type DeviceCapabilityName, type DeviceEvent, type DeviceMetadata, type DeviceNetworkStats, type DeviceState, type DeviceStoredEvent, type DeviceType, type DiscoveredDevice, type DoorbellEvent, type ElementState, type ElementStatus, type EncodedPacket, type EndpointCapabilities, type EventBufferStatus, type EventCluster, type EventFilter, type EventQuery, type EventQueryResult, type EventSnapshot, type EventSource, type ExposedResourceRequest, type ExposedResourceResponse, type ExposedResourceType, type FeatureFlag, type FeatureManifest, type FfmpegConfig, type FrameInput, type FrameSubscriptionOptions, type GlobalIdentity, type GroundTruth, type GroundTruthAnnotation, HF_BASE_URL, HF_REPO, type HeatmapData, type HeatmapOptions, type HttpMethod, type HubToAgentMessage, type IAccessory, type IAddonFileStorage, type IAddonHttpRoute, type IAddonModelManager, type IAddonPageProvider, type IAddonRouteProvider, type IAddonRouter, type IAddonStructuredStorage, type IAdminUI, type IAnalysisAddon, type IAnalysisDataPersistence, type IAnalysisPipeline, type IAnalysisStage, type IAudioClassifier, type IAudioDetector, type IAuthProvider, type ICamera, type ICameraAnalyticsProvider, type ICameraPipeline, type ICamstackAddon, type IClassFilterStage, type IClassifierProvider, type IConfigurable, type ICropperProvider, type IDecoderProvider, type IDecoderSession, type IDetectionAddon, type IDetectorProvider, type IDevice, type IDeviceCapability, type IDeviceProvider, type IDoorbell, type IElementConfig, type IEventBus, type IEventGenerationStage, type IEventPersistence, type IEvents, type IExposedResource, type IFaceDetector, type IFaceRecognizer, type IFileStorage, type IInferenceEngine, type IKnownFaceRepository, type IKnownFaces, type IKnownPlateRepository, type ILifecycleManaged, type ILogDestination, type IModelCatalogProvider, type IMotionSensor, type INetworkAccessProvider, type INetworkEndpoint, type INetworkProvider, type INetworkQualityTracker, type INotificationOutput, type IObjectDetector, type IObjectSnapshotStage, type IPanTiltZoom, type IPipelineConsumer, type IPipelineManager, type IPipelineRunner, type IPipelineSlotProvider, type IPipelineValidator, type IPlateDetector, type IPlateRecognizer, type IProcessManager, type IProviderConnectionTester, type IPythonEnvironment, type IRecognitionStage, type IRecognizer, type IRecording, type IRecordingAddon, type IRecordingCoordinator, type IRecordingDb, type IRemoteAccessProvider, type IReplEngine, type IResourceExposer, type IRestreamer, type IRetention, type IScopedLogger, type ISessionTracker, type ISiren, type IStatusLight, type IStorageBackend, type IStorageLocation, type IStorageProvider, type IStorageProviderLegacy, type IStreamBroker, type IStreamBrokerManager, type IStreamSourceAdapter, type IStreamingEngine, type IStructuredStorage, type ISubDetectionStage, type ISubDetector, type ISwitch, type ITaskHandler, type IToastService, type ITrackTrail, type ITrackerStage, type ITurnProvider, type ITwoWayAudio, type IWebRtcProvider, type IZoneAnalysisStage, type IZoneRepository, type KnownAudioEvent, type KnownFace, type KnownFaceEntry, type KnownPlate, type LabelDefinition, type Landmark, type LatencyStats, type LiveEvent, type LogEntry, type LogFilter, type LogLevel, type LoggerFactory, MACRO_LABELS, type ManagedProcessStatus, type ModelCatalogEntry, type ModelDownloadOptions, type ModelDownloadResult, type ModelFormat$1 as ModelFormat, type ModelFormatEntry, type ModelOutputFormat, type MultiBenchmarkReport, type NetworkAccessStatus, type NetworkAddress, type NormalizedSource, type Notification, type ObjectSnapshotResult, type ObjectState, type PersistableEvent, type PipelineConfig, type PipelineNode, type PipelineOutputFormat, type PipelineOutputStream, type PipelineResult, type PipelineSlot, type PipelineStatus, type PipelineStatusInfo, type ProbeCapability, type ProbeResult, type ProcessConfig, type ProcessStats, type ProviderStatus, type PtzPreset, type PublicAddress, type PythonEnvReady, type PythonProbeResult, type QueryFilter, RECOGNITION_TYPES, type RecognitionResult, type RecognitionType, type RecordingSegment, type RecordingSegmentInfo, type RegisteredStream, type RemoteAccessStatus, type RemoteBenchmarkConfig, type ReplContext, type ReplResult, type ReplScope, type RequiredStep, type ResolveEngineOptions, type ResourceAuthMode, type ResourceStats, type RestreamerExposedResource, type RetentionConfig, type RetentionReport, type RolePriority, type RouteAccess, type RouteHandler, type RouteRequest, type RouteResponse, SUB_DETECTION_TYPES, type ScopedToken, type ServerModelCatalogEntry, type ModelType as ServerModelType, type ServerTrackedDetection, type SessionTrack, type SourceAdapterStatus, type SourceCapabilities, type SpatialDetection, type StepError, type StepResult, type StorageConfig, type StorageLocationConfig, type StorageLocationName, type StorageRecord, type StreamFormat, type StreamInfo, type StreamNetworkStats, type StreamOption, type StreamSource, type StreamSourceOption, type StreamStatus, type StreamingSource, type SubDetection, type SubDetectionType, type SystemEvent, type SystemInfo, type TaskContext, type TaskDispatchOptions, type TaskHandlerSecurity, type TaskProgress, type TestConnectionResult, type TimeRange, type TimeRangeOptions, type Toast, type TokenPayload, type TokenScope, type TrackCaptureConfig, type TrackDetail, type TrackFilter, type TrackMediaFile, type TrackMediaType, type TrackPosition, type TrackSnapshot, type TrackTrail, type TrackedDetection, type TrackedObjectState, type TrackedObjectSummary, type TrackingInfo, type TurnCredentials, type TurnServer, type Unsubscribe, type UserPermissions, type UserRecord, type UserRole, type ValidationIssue, type ValidationResult, type VideoFrame, type VideoFrameCallback, type ZoneDefinition, type ZoneEvent, type ZoneHistoryPoint, type ZoneLiveState, cosineSimilarity, createAnalysisContext, enrichContext, hfModelUrl };