@ceralive/cerastream 2026.7.3 → 2026.7.5

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/README.md CHANGED
@@ -6,12 +6,14 @@ engine's JSON-RPC 2.0 / NDJSON control plane over a Unix domain socket.
6
6
 
7
7
  - JSON-RPC 2.0 envelope + `hello` handshake schemas
8
8
  - The eight v1 control methods (Zod params + result schemas + inferred types)
9
- - The eight server-push event payloads (discriminated union)
9
+ - The nine server-push event payloads (discriminated union)
10
10
  - Two-tier error codes (RPC + runtime)
11
11
  - A unified engine config schema (= `start` params)
12
12
  - A `CerastreamClient` interface + `connect()` factory (UDS transport)
13
13
  - Additive `client.getCapabilities()` discovery for platform, source, encoder, and
14
14
  local preview availability
15
+ - Additive `client.changeConfig()` — reconfigure the live session (resolution,
16
+ framerate, codec, pipeline, source) as one transaction with typed rollback
15
17
 
16
18
  The Zod schemas, types, and constants are the frozen wire contract; `connect()`
17
19
  drives that contract over an NDJSON/UDS transport with no native dependencies.
package/dist/client.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type HelloResult } from "./envelope.js";
2
2
  import { type EventParams } from "./events.js";
3
- import { type GetCapabilitiesResult, type ListDevicesParams, type ListDevicesResult, type PreviewSessionParams, type PreviewSessionResult, type ReloadConfigParams, type ReloadConfigResult, type SetBitrateParams, type SetBitrateResult, type StartParams, type StartResult, type StopParams, type StopResult, type SubscribeEventsParams, type SubscribeEventsResult, type SwitchInputParams, type SwitchInputResult } from "./messages.js";
3
+ import { type ChangeConfigParams, type ChangeConfigResult, type GetCapabilitiesResult, type ListDevicesParams, type ListDevicesResult, type PreviewSessionParams, type PreviewSessionResult, type ReloadConfigParams, type ReloadConfigResult, type SetBitrateParams, type SetBitrateResult, type StartParams, type StartResult, type StopParams, type StopResult, type SubscribeEventsParams, type SubscribeEventsResult, type SwitchInputParams, type SwitchInputResult } from "./messages.js";
4
4
  /** Options for {@link connect}. All optional — `connect({})` is valid. */
5
5
  export interface ConnectOptions {
6
6
  /** Control socket path override. Defaults to the resolved /run/cerastream/control.sock. */
@@ -82,6 +82,20 @@ export interface CerastreamClient {
82
82
  * availability. This request intentionally sits outside the frozen v1 map.
83
83
  */
84
84
  getCapabilities(): Promise<GetCapabilitiesResult>;
85
+ /**
86
+ * Reconfigure the LIVE session as one transaction (resolution, framerate,
87
+ * codec, pipeline, source). Also sits outside the frozen v1 map.
88
+ *
89
+ * Resolves for every phase the transaction reached — including
90
+ * `rollback_failed`, an honest terminal outcome. Callers must branch on
91
+ * `result.phase`, not on whether the promise settled. The engine's declared
92
+ * worst-case transaction bound is 65 000 ms, so size any caller-side deadline
93
+ * from that, not from the default per-request timeout.
94
+ * @param params A delta of the live config; absent field ⇒ keep live value.
95
+ * @throws {CerastreamRpcError} ONLY when the transaction never started (not
96
+ * streaming, a concurrent change, or an empty/invalid delta).
97
+ */
98
+ changeConfig(params: ChangeConfigParams): Promise<ChangeConfigResult>;
85
99
  /**
86
100
  * Subscribe to the live event stream; `handler` fires for each pushed event.
87
101
  * @param params The topics to subscribe to (absent ⇒ all topics).
package/dist/client.js CHANGED
@@ -4,7 +4,7 @@ import { existsSync } from "node:fs";
4
4
  import { CerastreamConnectionError, CerastreamRpcError, CerastreamTimeoutError, } from "./errors.js";
5
5
  import { eventParamsSchema } from "./events.js";
6
6
  import { controlSocketPath } from "./paths.js";
7
- import { getCapabilitiesResultSchema, requestSchemas, } from "./messages.js";
7
+ import { changeConfigParamsSchema, changeConfigResultSchema, getCapabilitiesResultSchema, requestSchemas, } from "./messages.js";
8
8
  import { LineSocket } from "./transport.js";
9
9
  const DEFAULTS = {
10
10
  requestTimeoutMs: 10_000,
@@ -82,6 +82,9 @@ class ClientImpl {
82
82
  async getCapabilities() {
83
83
  return getCapabilitiesResultSchema.parse(await this.rawRequest("get-capabilities", undefined));
84
84
  }
85
+ async changeConfig(params) {
86
+ return changeConfigResultSchema.parse(await this.rawRequest("change-config", changeConfigParamsSchema.parse(params)));
87
+ }
85
88
  previewSession(params) {
86
89
  return this.call("preview-session", params);
87
90
  }
@@ -8,7 +8,7 @@ export declare const PROTOCOL_VERSION: "cerastream-ipc/1";
8
8
  * additive-only within protocol major `cerastream-ipc/1` (ADR-0002 §4); this value
9
9
  * only moves when the wire schema itself does, in lockstep across both languages.
10
10
  */
11
- export declare const SCHEMA_VERSION: "0.8.0";
11
+ export declare const SCHEMA_VERSION: "0.11.0";
12
12
  /** Runtime dir holding both control + preview sockets. systemd `RuntimeDirectory=cerastream`. */
13
13
  export declare const DEFAULT_IPC_DIR: "/run/cerastream";
14
14
  /** Env override for the IPC dir (tests/dev). Defaults to {@link DEFAULT_IPC_DIR}. */
@@ -63,7 +63,7 @@ export declare const PROFILE_CATALOG_VERSION: "1.0.0";
63
63
  * ignores the field) never applies a semantic the caller assumed. Mirrors the
64
64
  * Rust `ENGINE_FEATURES`.
65
65
  */
66
- export declare const ENGINE_FEATURES: readonly ["video-passthrough"];
66
+ export declare const ENGINE_FEATURES: readonly ["video-passthrough", "input-mode"];
67
67
  /** Engine binary name (systemd-owned; CeraUI never spawns it — ADR-0005). */
68
68
  export declare const CERASTREAM_BIN: "cerastream";
69
69
  export declare const DEFAULT_MIN_BITRATE = 300;
package/dist/constants.js CHANGED
@@ -11,7 +11,7 @@ export const PROTOCOL_VERSION = "cerastream-ipc/1";
11
11
  * additive-only within protocol major `cerastream-ipc/1` (ADR-0002 §4); this value
12
12
  * only moves when the wire schema itself does, in lockstep across both languages.
13
13
  */
14
- export const SCHEMA_VERSION = "0.8.0";
14
+ export const SCHEMA_VERSION = "0.11.0";
15
15
  /** Runtime dir holding both control + preview sockets. systemd `RuntimeDirectory=cerastream`. */
16
16
  export const DEFAULT_IPC_DIR = "/run/cerastream";
17
17
  /** Env override for the IPC dir (tests/dev). Defaults to {@link DEFAULT_IPC_DIR}. */
@@ -72,7 +72,7 @@ export const PROFILE_CATALOG_VERSION = "1.0.0";
72
72
  * ignores the field) never applies a semantic the caller assumed. Mirrors the
73
73
  * Rust `ENGINE_FEATURES`.
74
74
  */
75
- export const ENGINE_FEATURES = ["video-passthrough"];
75
+ export const ENGINE_FEATURES = ["video-passthrough", "input-mode"];
76
76
  /** Engine binary name (systemd-owned; CeraUI never spawns it — ADR-0005). */
77
77
  export const CERASTREAM_BIN = "cerastream";
78
78
  // ---- config defaults (mirror ceracoder, the engine being replaced) ----
package/dist/errors.d.ts CHANGED
@@ -88,8 +88,21 @@ export declare const processErrorCodeSchema: z.ZodEnum<{
88
88
  pipeline_stall: "pipeline_stall";
89
89
  srt_connect_failed: "srt_connect_failed";
90
90
  srt_connection_lost: "srt_connection_lost";
91
+ capture_unrecoverable: "capture_unrecoverable";
91
92
  }>;
92
93
  export type ProcessErrorCode = z.infer<typeof processErrorCodeSchema>;
94
+ /**
95
+ * Why a capture device became `capture_unrecoverable` — the `reason` field of the
96
+ * `error` event. Mirrors the Rust `CaptureUnrecoverableReason`.
97
+ */
98
+ export declare const captureUnrecoverableReasonSchema: z.ZodEnum<{
99
+ node_absent_reprobe_disabled: "node_absent_reprobe_disabled";
100
+ reprobe_no_reenumeration: "reprobe_no_reenumeration";
101
+ reprobe_identity_changed: "reprobe_identity_changed";
102
+ reprobe_node_still_absent: "reprobe_node_still_absent";
103
+ reprobe_unavailable: "reprobe_unavailable";
104
+ }>;
105
+ export type CaptureUnrecoverableReason = z.infer<typeof captureUnrecoverableReasonSchema>;
93
106
  /** Origin of a Tier 2 runtime error (the notification channel CeraUI routes on). */
94
107
  export declare const processErrorSourceSchema: z.ZodEnum<{
95
108
  srtla: "srtla";
package/dist/errors.js CHANGED
@@ -124,6 +124,21 @@ export const processErrorCodeSchema = z.enum([
124
124
  "pipeline_stall",
125
125
  "srt_connect_failed",
126
126
  "srt_connection_lost",
127
+ // TERMINAL, unlike capture_video_error: the device is still on the bus but the
128
+ // engine has exhausted recovery, so a UI that keeps showing "reconnecting"
129
+ // would be lying. Always carries a captureUnrecoverableReasonSchema `reason`.
130
+ "capture_unrecoverable",
131
+ ]);
132
+ /**
133
+ * Why a capture device became `capture_unrecoverable` — the `reason` field of the
134
+ * `error` event. Mirrors the Rust `CaptureUnrecoverableReason`.
135
+ */
136
+ export const captureUnrecoverableReasonSchema = z.enum([
137
+ "node_absent_reprobe_disabled",
138
+ "reprobe_no_reenumeration",
139
+ "reprobe_identity_changed",
140
+ "reprobe_node_still_absent",
141
+ "reprobe_unavailable",
127
142
  ]);
128
143
  /** Origin of a Tier 2 runtime error (the notification channel CeraUI routes on). */
129
144
  export const processErrorSourceSchema = z.enum(["srtla", "engine"]);
package/dist/events.d.ts CHANGED
@@ -115,6 +115,25 @@ export declare const deviceEventSchema: z.ZodObject<{
115
115
  test: "test";
116
116
  network: "network";
117
117
  }>>;
118
+ modes: z.ZodOptional<z.ZodArray<z.ZodObject<{
119
+ media_type: z.ZodString;
120
+ pipeline_kind: z.ZodEnum<{
121
+ audio: "audio";
122
+ hdmi: "hdmi";
123
+ uvc_h264: "uvc_h264";
124
+ uvc_h265: "uvc_h265";
125
+ mjpeg: "mjpeg";
126
+ camlink: "camlink";
127
+ test: "test";
128
+ network: "network";
129
+ }>;
130
+ caps: z.ZodArray<z.ZodObject<{
131
+ width: z.ZodOptional<z.ZodNumber>;
132
+ height: z.ZodOptional<z.ZodNumber>;
133
+ framerate: z.ZodOptional<z.ZodString>;
134
+ media_type: z.ZodOptional<z.ZodString>;
135
+ }, z.core.$strip>>;
136
+ }, z.core.$strip>>>;
118
137
  alsa_card_id: z.ZodOptional<z.ZodString>;
119
138
  product_name: z.ZodOptional<z.ZodString>;
120
139
  transport: z.ZodOptional<z.ZodEnum<{
@@ -124,6 +143,8 @@ export declare const deviceEventSchema: z.ZodObject<{
124
143
  onboard: "onboard";
125
144
  }>>;
126
145
  stable_id: z.ZodOptional<z.ZodString>;
146
+ physical_group_id: z.ZodOptional<z.ZodString>;
147
+ hardware_serial: z.ZodOptional<z.ZodString>;
127
148
  }, z.core.$strip>;
128
149
  }, z.core.$strip>;
129
150
  /** Payload of a {@link deviceEventSchema} event. */
@@ -162,12 +183,14 @@ export declare const runtimeErrorEventSchema: z.ZodObject<{
162
183
  pipeline_stall: "pipeline_stall";
163
184
  srt_connect_failed: "srt_connect_failed";
164
185
  srt_connection_lost: "srt_connection_lost";
186
+ capture_unrecoverable: "capture_unrecoverable";
165
187
  }>;
166
188
  source: z.ZodEnum<{
167
189
  srtla: "srtla";
168
190
  engine: "engine";
169
191
  }>;
170
192
  reason: z.ZodOptional<z.ZodString>;
193
+ selected: z.ZodOptional<z.ZodBoolean>;
171
194
  }, z.core.$strip>;
172
195
  /** Payload of a {@link runtimeErrorEventSchema} event. */
173
196
  export type RuntimeErrorEvent = z.infer<typeof runtimeErrorEventSchema>;
@@ -214,6 +237,28 @@ export declare const audioLevelEventSchema: z.ZodObject<{
214
237
  }, z.core.$strip>;
215
238
  /** Payload of a {@link audioLevelEventSchema} event. */
216
239
  export type AudioLevelEvent = z.infer<typeof audioLevelEventSchema>;
240
+ /**
241
+ * `config-change` event — one phase of a `change-config` transaction (0.10.0,
242
+ * additive Todo 9). Exactly one `applying` at entry, then exactly ONE terminal
243
+ * phase for the same `attempt_id` (which the RPC result also echoes, so a caller
244
+ * that missed an event can still correlate). A terminal phase is published only
245
+ * after the outcome gate resolved, so `applied` means "PLAYING **and** frames
246
+ * actually advancing", never merely "PLAYING".
247
+ */
248
+ export declare const configChangeEventSchema: z.ZodObject<{
249
+ type: z.ZodLiteral<"config-change">;
250
+ seq: z.ZodNumber;
251
+ attempt_id: z.ZodString;
252
+ phase: z.ZodEnum<{
253
+ applying: "applying";
254
+ applied: "applied";
255
+ reverted: "reverted";
256
+ rollback_failed: "rollback_failed";
257
+ }>;
258
+ reason: z.ZodOptional<z.ZodString>;
259
+ }, z.core.$strip>;
260
+ /** Payload of a {@link configChangeEventSchema} event. */
261
+ export type ConfigChangeEvent = z.infer<typeof configChangeEventSchema>;
217
262
  /** Discriminated union of every v1 event payload (the inner `params`). */
218
263
  export declare const eventParamsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
219
264
  type: z.ZodLiteral<"status">;
@@ -288,6 +333,25 @@ export declare const eventParamsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
288
333
  test: "test";
289
334
  network: "network";
290
335
  }>>;
336
+ modes: z.ZodOptional<z.ZodArray<z.ZodObject<{
337
+ media_type: z.ZodString;
338
+ pipeline_kind: z.ZodEnum<{
339
+ audio: "audio";
340
+ hdmi: "hdmi";
341
+ uvc_h264: "uvc_h264";
342
+ uvc_h265: "uvc_h265";
343
+ mjpeg: "mjpeg";
344
+ camlink: "camlink";
345
+ test: "test";
346
+ network: "network";
347
+ }>;
348
+ caps: z.ZodArray<z.ZodObject<{
349
+ width: z.ZodOptional<z.ZodNumber>;
350
+ height: z.ZodOptional<z.ZodNumber>;
351
+ framerate: z.ZodOptional<z.ZodString>;
352
+ media_type: z.ZodOptional<z.ZodString>;
353
+ }, z.core.$strip>>;
354
+ }, z.core.$strip>>>;
291
355
  alsa_card_id: z.ZodOptional<z.ZodString>;
292
356
  product_name: z.ZodOptional<z.ZodString>;
293
357
  transport: z.ZodOptional<z.ZodEnum<{
@@ -297,6 +361,8 @@ export declare const eventParamsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
297
361
  onboard: "onboard";
298
362
  }>>;
299
363
  stable_id: z.ZodOptional<z.ZodString>;
364
+ physical_group_id: z.ZodOptional<z.ZodString>;
365
+ hardware_serial: z.ZodOptional<z.ZodString>;
300
366
  }, z.core.$strip>;
301
367
  }, z.core.$strip>, z.ZodObject<{
302
368
  type: z.ZodLiteral<"bitrate">;
@@ -320,12 +386,14 @@ export declare const eventParamsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
320
386
  pipeline_stall: "pipeline_stall";
321
387
  srt_connect_failed: "srt_connect_failed";
322
388
  srt_connection_lost: "srt_connection_lost";
389
+ capture_unrecoverable: "capture_unrecoverable";
323
390
  }>;
324
391
  source: z.ZodEnum<{
325
392
  srtla: "srtla";
326
393
  engine: "engine";
327
394
  }>;
328
395
  reason: z.ZodOptional<z.ZodString>;
396
+ selected: z.ZodOptional<z.ZodBoolean>;
329
397
  }, z.core.$strip>, z.ZodObject<{
330
398
  type: z.ZodLiteral<"preview">;
331
399
  seq: z.ZodNumber;
@@ -352,6 +420,17 @@ export declare const eventParamsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
352
420
  mode_none: "mode_none";
353
421
  handoff: "handoff";
354
422
  }>>;
423
+ }, z.core.$strip>, z.ZodObject<{
424
+ type: z.ZodLiteral<"config-change">;
425
+ seq: z.ZodNumber;
426
+ attempt_id: z.ZodString;
427
+ phase: z.ZodEnum<{
428
+ applying: "applying";
429
+ applied: "applied";
430
+ reverted: "reverted";
431
+ rollback_failed: "rollback_failed";
432
+ }>;
433
+ reason: z.ZodOptional<z.ZodString>;
355
434
  }, z.core.$strip>], "type">;
356
435
  export type EventParams = z.infer<typeof eventParamsSchema>;
357
436
  /** Full event envelope: { jsonrpc, method:"event", params } with typed params. */
@@ -431,6 +510,25 @@ export declare const cerastreamEventSchema: z.ZodObject<{
431
510
  test: "test";
432
511
  network: "network";
433
512
  }>>;
513
+ modes: z.ZodOptional<z.ZodArray<z.ZodObject<{
514
+ media_type: z.ZodString;
515
+ pipeline_kind: z.ZodEnum<{
516
+ audio: "audio";
517
+ hdmi: "hdmi";
518
+ uvc_h264: "uvc_h264";
519
+ uvc_h265: "uvc_h265";
520
+ mjpeg: "mjpeg";
521
+ camlink: "camlink";
522
+ test: "test";
523
+ network: "network";
524
+ }>;
525
+ caps: z.ZodArray<z.ZodObject<{
526
+ width: z.ZodOptional<z.ZodNumber>;
527
+ height: z.ZodOptional<z.ZodNumber>;
528
+ framerate: z.ZodOptional<z.ZodString>;
529
+ media_type: z.ZodOptional<z.ZodString>;
530
+ }, z.core.$strip>>;
531
+ }, z.core.$strip>>>;
434
532
  alsa_card_id: z.ZodOptional<z.ZodString>;
435
533
  product_name: z.ZodOptional<z.ZodString>;
436
534
  transport: z.ZodOptional<z.ZodEnum<{
@@ -440,6 +538,8 @@ export declare const cerastreamEventSchema: z.ZodObject<{
440
538
  onboard: "onboard";
441
539
  }>>;
442
540
  stable_id: z.ZodOptional<z.ZodString>;
541
+ physical_group_id: z.ZodOptional<z.ZodString>;
542
+ hardware_serial: z.ZodOptional<z.ZodString>;
443
543
  }, z.core.$strip>;
444
544
  }, z.core.$strip>, z.ZodObject<{
445
545
  type: z.ZodLiteral<"bitrate">;
@@ -463,12 +563,14 @@ export declare const cerastreamEventSchema: z.ZodObject<{
463
563
  pipeline_stall: "pipeline_stall";
464
564
  srt_connect_failed: "srt_connect_failed";
465
565
  srt_connection_lost: "srt_connection_lost";
566
+ capture_unrecoverable: "capture_unrecoverable";
466
567
  }>;
467
568
  source: z.ZodEnum<{
468
569
  srtla: "srtla";
469
570
  engine: "engine";
470
571
  }>;
471
572
  reason: z.ZodOptional<z.ZodString>;
573
+ selected: z.ZodOptional<z.ZodBoolean>;
472
574
  }, z.core.$strip>, z.ZodObject<{
473
575
  type: z.ZodLiteral<"preview">;
474
576
  seq: z.ZodNumber;
@@ -495,12 +597,24 @@ export declare const cerastreamEventSchema: z.ZodObject<{
495
597
  mode_none: "mode_none";
496
598
  handoff: "handoff";
497
599
  }>>;
600
+ }, z.core.$strip>, z.ZodObject<{
601
+ type: z.ZodLiteral<"config-change">;
602
+ seq: z.ZodNumber;
603
+ attempt_id: z.ZodString;
604
+ phase: z.ZodEnum<{
605
+ applying: "applying";
606
+ applied: "applied";
607
+ reverted: "reverted";
608
+ rollback_failed: "rollback_failed";
609
+ }>;
610
+ reason: z.ZodOptional<z.ZodString>;
498
611
  }, z.core.$strip>], "type">;
499
612
  }, z.core.$strip>;
500
613
  export type CerastreamEvent = z.infer<typeof cerastreamEventSchema>;
501
- /** The eight event topics (schema.md "Events" table) — count-assertion source.
502
- * `audio-level` is additive (Todo 21) and appended last, preserving order. */
503
- export declare const EVENT_TOPICS: readonly ["status", "switch", "device", "bitrate", "srt-stats", "error", "preview", "audio-level"];
614
+ /** The nine event topics (schema.md "Events" table) — count-assertion source.
615
+ * `audio-level` (Todo 21) and `config-change` (Todo 9) are additive and appended
616
+ * last, preserving the order of every prior topic. */
617
+ export declare const EVENT_TOPICS: readonly ["status", "switch", "device", "bitrate", "srt-stats", "error", "preview", "audio-level", "config-change"];
504
618
  export type EventTopicName = (typeof EVENT_TOPICS)[number];
505
619
  /** topic → payload Zod schema. Tests assert every topic is represented here. */
506
620
  export declare const eventSchemas: {
@@ -579,6 +693,25 @@ export declare const eventSchemas: {
579
693
  test: "test";
580
694
  network: "network";
581
695
  }>>;
696
+ modes: z.ZodOptional<z.ZodArray<z.ZodObject<{
697
+ media_type: z.ZodString;
698
+ pipeline_kind: z.ZodEnum<{
699
+ audio: "audio";
700
+ hdmi: "hdmi";
701
+ uvc_h264: "uvc_h264";
702
+ uvc_h265: "uvc_h265";
703
+ mjpeg: "mjpeg";
704
+ camlink: "camlink";
705
+ test: "test";
706
+ network: "network";
707
+ }>;
708
+ caps: z.ZodArray<z.ZodObject<{
709
+ width: z.ZodOptional<z.ZodNumber>;
710
+ height: z.ZodOptional<z.ZodNumber>;
711
+ framerate: z.ZodOptional<z.ZodString>;
712
+ media_type: z.ZodOptional<z.ZodString>;
713
+ }, z.core.$strip>>;
714
+ }, z.core.$strip>>>;
582
715
  alsa_card_id: z.ZodOptional<z.ZodString>;
583
716
  product_name: z.ZodOptional<z.ZodString>;
584
717
  transport: z.ZodOptional<z.ZodEnum<{
@@ -588,6 +721,8 @@ export declare const eventSchemas: {
588
721
  onboard: "onboard";
589
722
  }>>;
590
723
  stable_id: z.ZodOptional<z.ZodString>;
724
+ physical_group_id: z.ZodOptional<z.ZodString>;
725
+ hardware_serial: z.ZodOptional<z.ZodString>;
591
726
  }, z.core.$strip>;
592
727
  }, z.core.$strip>;
593
728
  readonly bitrate: z.ZodObject<{
@@ -614,12 +749,14 @@ export declare const eventSchemas: {
614
749
  pipeline_stall: "pipeline_stall";
615
750
  srt_connect_failed: "srt_connect_failed";
616
751
  srt_connection_lost: "srt_connection_lost";
752
+ capture_unrecoverable: "capture_unrecoverable";
617
753
  }>;
618
754
  source: z.ZodEnum<{
619
755
  srtla: "srtla";
620
756
  engine: "engine";
621
757
  }>;
622
758
  reason: z.ZodOptional<z.ZodString>;
759
+ selected: z.ZodOptional<z.ZodBoolean>;
623
760
  }, z.core.$strip>;
624
761
  readonly preview: z.ZodObject<{
625
762
  type: z.ZodLiteral<"preview">;
@@ -649,4 +786,16 @@ export declare const eventSchemas: {
649
786
  handoff: "handoff";
650
787
  }>>;
651
788
  }, z.core.$strip>;
789
+ readonly "config-change": z.ZodObject<{
790
+ type: z.ZodLiteral<"config-change">;
791
+ seq: z.ZodNumber;
792
+ attempt_id: z.ZodString;
793
+ phase: z.ZodEnum<{
794
+ applying: "applying";
795
+ applied: "applied";
796
+ reverted: "reverted";
797
+ rollback_failed: "rollback_failed";
798
+ }>;
799
+ reason: z.ZodOptional<z.ZodString>;
800
+ }, z.core.$strip>;
652
801
  };
package/dist/events.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { processErrorCodeSchema, processErrorSourceSchema, } from "./errors.js";
3
- import { captureDeviceSchema, inputModeSchema, mediaClassSchema, streamStateSchema, } from "./types.js";
3
+ import { captureDeviceSchema, configChangePhaseSchema, inputModeSchema, mediaClassSchema, streamStateSchema, } from "./types.js";
4
4
  // Server → client `event` notifications, delivered after `subscribe-events`
5
5
  // (schema.md "Events"). Each event is the inner `params` of an rpcEventSchema:
6
6
  // { type, seq, … }. `type` discriminates; `seq` is a per-type monotonic counter.
@@ -93,6 +93,7 @@ export const runtimeErrorEventSchema = z.object({
93
93
  code: processErrorCodeSchema, // Tier 2 (errors.ts)
94
94
  source: processErrorSourceSchema,
95
95
  reason: z.string().optional(), // structured replacement for the stderr <reason>
96
+ selected: z.boolean().optional(), // additive (0.11.0): the degraded input is the operator's OWN selection, so the stream will not move itself and only they can pick another — a materially different operator response than a degraded non-selected input, and one to branch on rather than string-match. Absent ⇒ false
96
97
  });
97
98
  /** `preview` event — a preview-session lifecycle/phase change. */
98
99
  export const previewEventSchema = z.object({
@@ -127,6 +128,21 @@ export const audioLevelEventSchema = z.object({
127
128
  unavailable: z.literal(true).optional(),
128
129
  reason: z.enum(["device_busy", "no_device", "mode_none", "handoff"]).optional(),
129
130
  });
131
+ /**
132
+ * `config-change` event — one phase of a `change-config` transaction (0.10.0,
133
+ * additive Todo 9). Exactly one `applying` at entry, then exactly ONE terminal
134
+ * phase for the same `attempt_id` (which the RPC result also echoes, so a caller
135
+ * that missed an event can still correlate). A terminal phase is published only
136
+ * after the outcome gate resolved, so `applied` means "PLAYING **and** frames
137
+ * actually advancing", never merely "PLAYING".
138
+ */
139
+ export const configChangeEventSchema = z.object({
140
+ type: z.literal("config-change"),
141
+ seq,
142
+ attempt_id: z.string(),
143
+ phase: configChangePhaseSchema,
144
+ reason: z.string().optional(), // absent on applying/applied; "teardown_timeout" marks the supervisor escalation
145
+ });
130
146
  /** Discriminated union of every v1 event payload (the inner `params`). */
131
147
  export const eventParamsSchema = z.discriminatedUnion("type", [
132
148
  statusEventSchema,
@@ -137,6 +153,7 @@ export const eventParamsSchema = z.discriminatedUnion("type", [
137
153
  runtimeErrorEventSchema,
138
154
  previewEventSchema,
139
155
  audioLevelEventSchema,
156
+ configChangeEventSchema,
140
157
  ]);
141
158
  /** Full event envelope: { jsonrpc, method:"event", params } with typed params. */
142
159
  export const cerastreamEventSchema = z.object({
@@ -144,8 +161,9 @@ export const cerastreamEventSchema = z.object({
144
161
  method: z.literal("event"),
145
162
  params: eventParamsSchema,
146
163
  });
147
- /** The eight event topics (schema.md "Events" table) — count-assertion source.
148
- * `audio-level` is additive (Todo 21) and appended last, preserving order. */
164
+ /** The nine event topics (schema.md "Events" table) — count-assertion source.
165
+ * `audio-level` (Todo 21) and `config-change` (Todo 9) are additive and appended
166
+ * last, preserving the order of every prior topic. */
149
167
  export const EVENT_TOPICS = [
150
168
  "status",
151
169
  "switch",
@@ -155,6 +173,7 @@ export const EVENT_TOPICS = [
155
173
  "error",
156
174
  "preview",
157
175
  "audio-level",
176
+ "config-change",
158
177
  ];
159
178
  /** topic → payload Zod schema. Tests assert every topic is represented here. */
160
179
  export const eventSchemas = {
@@ -166,4 +185,5 @@ export const eventSchemas = {
166
185
  error: runtimeErrorEventSchema,
167
186
  preview: previewEventSchema,
168
187
  "audio-level": audioLevelEventSchema,
188
+ "config-change": configChangeEventSchema,
169
189
  };
@@ -42,6 +42,16 @@ export declare const startParamsSchema: z.ZodObject<{
42
42
  force: "force";
43
43
  off: "off";
44
44
  }>>;
45
+ input_mode: z.ZodOptional<z.ZodEnum<{
46
+ audio: "audio";
47
+ hdmi: "hdmi";
48
+ uvc_h264: "uvc_h264";
49
+ uvc_h265: "uvc_h265";
50
+ mjpeg: "mjpeg";
51
+ camlink: "camlink";
52
+ test: "test";
53
+ network: "network";
54
+ }>>;
45
55
  }, z.core.$strip>;
46
56
  export type StartParams = z.infer<typeof startParamsSchema>;
47
57
  export declare const startResultSchema: z.ZodObject<{
@@ -83,6 +93,7 @@ export declare const reloadConfigParamsSchema: z.ZodObject<{
83
93
  audio: z.ZodOptional<z.ZodObject<{
84
94
  delay_ms: z.ZodOptional<z.ZodNumber>;
85
95
  delay_ms_signed: z.ZodOptional<z.ZodNumber>;
96
+ meter_device: z.ZodOptional<z.ZodNullable<z.ZodString>>;
86
97
  }, z.core.$strip>>;
87
98
  }, z.core.$strip>;
88
99
  export type ReloadConfigParams = z.infer<typeof reloadConfigParamsSchema>;
@@ -103,6 +114,7 @@ export declare const reloadConfigResultSchema: z.ZodObject<{
103
114
  audio: z.ZodOptional<z.ZodObject<{
104
115
  delay_ms: z.ZodOptional<z.ZodNumber>;
105
116
  delay_ms_signed: z.ZodOptional<z.ZodNumber>;
117
+ meter_device: z.ZodOptional<z.ZodNullable<z.ZodString>>;
106
118
  }, z.core.$strip>>;
107
119
  }, z.core.$strip>;
108
120
  bitrate_control: z.ZodOptional<z.ZodEnum<{
@@ -191,6 +203,25 @@ export declare const listDevicesResultSchema: z.ZodObject<{
191
203
  test: "test";
192
204
  network: "network";
193
205
  }>>;
206
+ modes: z.ZodOptional<z.ZodArray<z.ZodObject<{
207
+ media_type: z.ZodString;
208
+ pipeline_kind: z.ZodEnum<{
209
+ audio: "audio";
210
+ hdmi: "hdmi";
211
+ uvc_h264: "uvc_h264";
212
+ uvc_h265: "uvc_h265";
213
+ mjpeg: "mjpeg";
214
+ camlink: "camlink";
215
+ test: "test";
216
+ network: "network";
217
+ }>;
218
+ caps: z.ZodArray<z.ZodObject<{
219
+ width: z.ZodOptional<z.ZodNumber>;
220
+ height: z.ZodOptional<z.ZodNumber>;
221
+ framerate: z.ZodOptional<z.ZodString>;
222
+ media_type: z.ZodOptional<z.ZodString>;
223
+ }, z.core.$strip>>;
224
+ }, z.core.$strip>>>;
194
225
  alsa_card_id: z.ZodOptional<z.ZodString>;
195
226
  product_name: z.ZodOptional<z.ZodString>;
196
227
  transport: z.ZodOptional<z.ZodEnum<{
@@ -200,6 +231,8 @@ export declare const listDevicesResultSchema: z.ZodObject<{
200
231
  onboard: "onboard";
201
232
  }>>;
202
233
  stable_id: z.ZodOptional<z.ZodString>;
234
+ physical_group_id: z.ZodOptional<z.ZodString>;
235
+ hardware_serial: z.ZodOptional<z.ZodString>;
203
236
  }, z.core.$strip>>;
204
237
  }, z.core.$strip>;
205
238
  export type ListDevicesResult = z.infer<typeof listDevicesResultSchema>;
@@ -212,6 +245,7 @@ export declare const eventTopicSchema: z.ZodEnum<{
212
245
  "srt-stats": "srt-stats";
213
246
  preview: "preview";
214
247
  "audio-level": "audio-level";
248
+ "config-change": "config-change";
215
249
  }>;
216
250
  export type EventTopic = z.infer<typeof eventTopicSchema>;
217
251
  export declare const subscribeEventsParamsSchema: z.ZodObject<{
@@ -224,6 +258,7 @@ export declare const subscribeEventsParamsSchema: z.ZodObject<{
224
258
  "srt-stats": "srt-stats";
225
259
  preview: "preview";
226
260
  "audio-level": "audio-level";
261
+ "config-change": "config-change";
227
262
  }>>>;
228
263
  }, z.core.$strip>;
229
264
  export type SubscribeEventsParams = z.infer<typeof subscribeEventsParamsSchema>;
@@ -237,6 +272,7 @@ export declare const subscribeEventsResultSchema: z.ZodObject<{
237
272
  "srt-stats": "srt-stats";
238
273
  preview: "preview";
239
274
  "audio-level": "audio-level";
275
+ "config-change": "config-change";
240
276
  }>>;
241
277
  }, z.core.$strip>;
242
278
  export type SubscribeEventsResult = z.infer<typeof subscribeEventsResultSchema>;
@@ -373,6 +409,52 @@ export declare const getCapabilitiesResultSchema: z.ZodObject<{
373
409
  features: z.ZodOptional<z.ZodArray<z.ZodString>>;
374
410
  }, z.core.$strip>;
375
411
  export type GetCapabilitiesResult = z.infer<typeof getCapabilitiesResultSchema>;
412
+ export declare const changeConfigParamsSchema: z.ZodObject<{
413
+ pipeline: z.ZodOptional<z.ZodString>;
414
+ resolution: z.ZodOptional<z.ZodString>;
415
+ framerate: z.ZodOptional<z.ZodNumber>;
416
+ codec: z.ZodOptional<z.ZodEnum<{
417
+ h264: "h264";
418
+ h265: "h265";
419
+ }>>;
420
+ input_id: z.ZodOptional<z.ZodString>;
421
+ input_mode: z.ZodOptional<z.ZodEnum<{
422
+ audio: "audio";
423
+ hdmi: "hdmi";
424
+ uvc_h264: "uvc_h264";
425
+ uvc_h265: "uvc_h265";
426
+ mjpeg: "mjpeg";
427
+ camlink: "camlink";
428
+ test: "test";
429
+ network: "network";
430
+ }>>;
431
+ }, z.core.$strip>;
432
+ export type ChangeConfigParams = z.infer<typeof changeConfigParamsSchema>;
433
+ export declare const changeConfigResultSchema: z.ZodObject<{
434
+ attempt_id: z.ZodString;
435
+ phase: z.ZodEnum<{
436
+ applying: "applying";
437
+ applied: "applied";
438
+ reverted: "reverted";
439
+ rollback_failed: "rollback_failed";
440
+ }>;
441
+ state: z.ZodEnum<{
442
+ idle: "idle";
443
+ starting: "starting";
444
+ streaming: "streaming";
445
+ stopping: "stopping";
446
+ }>;
447
+ reason: z.ZodOptional<z.ZodString>;
448
+ }, z.core.$strip>;
449
+ export type ChangeConfigResult = z.infer<typeof changeConfigResultSchema>;
450
+ /**
451
+ * The `reason` a `rollback_failed` carries when a teardown deadline overran.
452
+ * A TERMINAL supervisor escalation, not an ordinary failure: the engine could not
453
+ * prove the old session released its capture devices within the bound, so it
454
+ * refuses to build a second session that would race it. Mirrors the Rust
455
+ * `REASON_TEARDOWN_TIMEOUT`; consumers render it distinctly.
456
+ */
457
+ export declare const REASON_TEARDOWN_TIMEOUT: "teardown_timeout";
376
458
  /** The eight v1 control methods (the literal JSON-RPC `method` strings). */
377
459
  export declare const V1_METHODS: readonly ["start", "stop", "reload-config", "set-bitrate", "switch-input", "list-devices", "subscribe-events", "preview-session"];
378
460
  export type V1Method = (typeof V1_METHODS)[number];
@@ -437,6 +519,16 @@ export declare const requestSchemas: {
437
519
  force: "force";
438
520
  off: "off";
439
521
  }>>;
522
+ input_mode: z.ZodOptional<z.ZodEnum<{
523
+ audio: "audio";
524
+ hdmi: "hdmi";
525
+ uvc_h264: "uvc_h264";
526
+ uvc_h265: "uvc_h265";
527
+ mjpeg: "mjpeg";
528
+ camlink: "camlink";
529
+ test: "test";
530
+ network: "network";
531
+ }>>;
440
532
  }, z.core.$strip>;
441
533
  readonly result: z.ZodObject<{
442
534
  session_id: z.ZodString;
@@ -478,6 +570,7 @@ export declare const requestSchemas: {
478
570
  audio: z.ZodOptional<z.ZodObject<{
479
571
  delay_ms: z.ZodOptional<z.ZodNumber>;
480
572
  delay_ms_signed: z.ZodOptional<z.ZodNumber>;
573
+ meter_device: z.ZodOptional<z.ZodNullable<z.ZodString>>;
481
574
  }, z.core.$strip>>;
482
575
  }, z.core.$strip>;
483
576
  readonly result: z.ZodObject<{
@@ -497,6 +590,7 @@ export declare const requestSchemas: {
497
590
  audio: z.ZodOptional<z.ZodObject<{
498
591
  delay_ms: z.ZodOptional<z.ZodNumber>;
499
592
  delay_ms_signed: z.ZodOptional<z.ZodNumber>;
593
+ meter_device: z.ZodOptional<z.ZodNullable<z.ZodString>>;
500
594
  }, z.core.$strip>>;
501
595
  }, z.core.$strip>;
502
596
  bitrate_control: z.ZodOptional<z.ZodEnum<{
@@ -569,6 +663,25 @@ export declare const requestSchemas: {
569
663
  test: "test";
570
664
  network: "network";
571
665
  }>>;
666
+ modes: z.ZodOptional<z.ZodArray<z.ZodObject<{
667
+ media_type: z.ZodString;
668
+ pipeline_kind: z.ZodEnum<{
669
+ audio: "audio";
670
+ hdmi: "hdmi";
671
+ uvc_h264: "uvc_h264";
672
+ uvc_h265: "uvc_h265";
673
+ mjpeg: "mjpeg";
674
+ camlink: "camlink";
675
+ test: "test";
676
+ network: "network";
677
+ }>;
678
+ caps: z.ZodArray<z.ZodObject<{
679
+ width: z.ZodOptional<z.ZodNumber>;
680
+ height: z.ZodOptional<z.ZodNumber>;
681
+ framerate: z.ZodOptional<z.ZodString>;
682
+ media_type: z.ZodOptional<z.ZodString>;
683
+ }, z.core.$strip>>;
684
+ }, z.core.$strip>>>;
572
685
  alsa_card_id: z.ZodOptional<z.ZodString>;
573
686
  product_name: z.ZodOptional<z.ZodString>;
574
687
  transport: z.ZodOptional<z.ZodEnum<{
@@ -578,6 +691,8 @@ export declare const requestSchemas: {
578
691
  onboard: "onboard";
579
692
  }>>;
580
693
  stable_id: z.ZodOptional<z.ZodString>;
694
+ physical_group_id: z.ZodOptional<z.ZodString>;
695
+ hardware_serial: z.ZodOptional<z.ZodString>;
581
696
  }, z.core.$strip>>;
582
697
  }, z.core.$strip>;
583
698
  };
@@ -592,6 +707,7 @@ export declare const requestSchemas: {
592
707
  "srt-stats": "srt-stats";
593
708
  preview: "preview";
594
709
  "audio-level": "audio-level";
710
+ "config-change": "config-change";
595
711
  }>>>;
596
712
  }, z.core.$strip>;
597
713
  readonly result: z.ZodObject<{
@@ -604,6 +720,7 @@ export declare const requestSchemas: {
604
720
  "srt-stats": "srt-stats";
605
721
  preview: "preview";
606
722
  "audio-level": "audio-level";
723
+ "config-change": "config-change";
607
724
  }>>;
608
725
  }, z.core.$strip>;
609
726
  };
package/dist/messages.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { AUDIO_DELAY_MAX_MS } from "./constants.js";
3
3
  import { helloParamsSchema, helloResultSchema, } from "./envelope.js";
4
- import { balancerAlgorithmSchema, bitrateControlSchema, captureDeviceSchema, cerastreamConfigSchema, inputModeSchema, mediaClassSchema, previewTierSchema, streamStateSchema, } from "./types.js";
4
+ import { balancerAlgorithmSchema, bitrateControlSchema, captureDeviceKindSchema, captureDeviceSchema, cerastreamConfigSchema, configChangePhaseSchema, inputModeSchema, mediaClassSchema, previewTierSchema, streamStateSchema, videoCodecSchema, } from "./types.js";
5
5
  // The eight v1 control methods (schema.md "v1 messages"). Each is matched
6
6
  // byte-for-byte to schema.md: the JSON shape on the wire is the contract.
7
7
  // ---- 1. start ----
@@ -39,6 +39,10 @@ export const reloadConfigParamsSchema = z.object({
39
39
  .object({
40
40
  delay_ms: z.number().int().min(0).max(AUDIO_DELAY_MAX_MS).optional(), // legacy unsigned; kept for 0.3.0 callers
41
41
  delay_ms_signed: z.number().int().optional(), // signed sibling; clamped at apply, so unbounded
42
+ // additive (0.9.0): idle-meter card preference — absent leaves it unchanged,
43
+ // `null` restores the engine's auto-pick, `hw:CARD=…` prefers that card
44
+ // (a preference, not a pin: a card that never delivers is still demoted)
45
+ meter_device: z.string().nullable().optional(),
42
46
  })
43
47
  .optional(),
44
48
  });
@@ -100,6 +104,7 @@ export const eventTopicSchema = z.enum([
100
104
  "error",
101
105
  "preview",
102
106
  "audio-level",
107
+ "config-change",
103
108
  ]);
104
109
  export const subscribeEventsParamsSchema = z.object({
105
110
  topics: z.array(eventTopicSchema).optional(), // default: all topics
@@ -188,6 +193,49 @@ export const getCapabilitiesResultSchema = z.object({
188
193
  network_embedded_audio: z.boolean().optional(), // engine routes network-ingest embedded audio to the mux
189
194
  features: z.array(z.string()).optional(), // named engine features (e.g. "video-passthrough") for fail-closed negotiation
190
195
  });
196
+ // ---- 10. change-config (0.10.0, additive) ----
197
+ // Deliberately absent from V1_METHODS / requestSchemas, like get-capabilities and
198
+ // switch-audio, so the frozen eight-method contract count stays eight. Params are
199
+ // a DELTA of startParamsSchema: absent ⇒ keep the live value. An empty delta is
200
+ // refused with `cerastream.params.invalid` — a no-op that restarted the pipeline
201
+ // would be the worst possible outcome. `input_id` rides the transaction, so a
202
+ // source change renegotiates caps instead of going through `switch-input`.
203
+ export const changeConfigParamsSchema = z
204
+ .object({
205
+ pipeline: z.string().optional(),
206
+ resolution: z.string().optional(), // "WxH" pixels, e.g. "3840x2160"
207
+ framerate: z.number().optional(),
208
+ codec: videoCodecSchema.optional(),
209
+ input_id: z.string().optional(),
210
+ // Switching a LIVE device between a libuvc family (uvc_h264/uvc_h265) and
211
+ // the v4l2 mjpeg family is NOT a plain rebuild: libuvc detaches uvcvideo, so
212
+ // the /dev/videoN node the mjpeg leg needs does not exist until the hold is
213
+ // released — and it then returns asynchronously, possibly renumbered. The
214
+ // engine runs a release → re-enumeration-barrier → open transaction and
215
+ // rolls back honestly (reason: mode_barrier_timeout) if the barrier expires.
216
+ input_mode: captureDeviceKindSchema.optional(),
217
+ })
218
+ .refine((delta) => Object.keys(delta).length > 0, {
219
+ message: "change-config delta must carry at least one field",
220
+ });
221
+ // `Ok` for every phase the transaction actually reached — INCLUDING
222
+ // `rollback_failed`, an honest terminal outcome rather than an RPC fault. A
223
+ // Tier-1 error means the transaction never started, so a caller can tell
224
+ // "nothing happened" from "something happened and here is what".
225
+ export const changeConfigResultSchema = z.object({
226
+ attempt_id: z.string(), // correlates with every config-change event of the attempt
227
+ phase: configChangePhaseSchema, // the TERMINAL phase reached
228
+ state: streamStateSchema, // streaming for applied/reverted, idle for rollback_failed
229
+ reason: z.string().optional(), // machine-stable cause on a non-applied phase
230
+ });
231
+ /**
232
+ * The `reason` a `rollback_failed` carries when a teardown deadline overran.
233
+ * A TERMINAL supervisor escalation, not an ordinary failure: the engine could not
234
+ * prove the old session released its capture devices within the bound, so it
235
+ * refuses to build a second session that would race it. Mirrors the Rust
236
+ * `REASON_TEARDOWN_TIMEOUT`; consumers render it distinctly.
237
+ */
238
+ export const REASON_TEARDOWN_TIMEOUT = "teardown_timeout";
191
239
  // ---- method registry (count-assertion source of truth) ----
192
240
  /** The eight v1 control methods (the literal JSON-RPC `method` strings). */
193
241
  export const V1_METHODS = [
package/dist/types.d.ts CHANGED
@@ -61,6 +61,13 @@ export declare const deviceTransportSchema: z.ZodEnum<{
61
61
  onboard: "onboard";
62
62
  }>;
63
63
  export type DeviceTransport = z.infer<typeof deviceTransportSchema>;
64
+ export declare const configChangePhaseSchema: z.ZodEnum<{
65
+ applying: "applying";
66
+ applied: "applied";
67
+ reverted: "reverted";
68
+ rollback_failed: "rollback_failed";
69
+ }>;
70
+ export type ConfigChangePhase = z.infer<typeof configChangePhaseSchema>;
64
71
  /** SRT transport config. Mirrors schema.md `start.srt` exactly. */
65
72
  export declare const srtConfigSchema: z.ZodObject<{
66
73
  host: z.ZodString;
@@ -157,6 +164,16 @@ export declare const cerastreamConfigSchema: z.ZodObject<{
157
164
  force: "force";
158
165
  off: "off";
159
166
  }>>;
167
+ input_mode: z.ZodOptional<z.ZodEnum<{
168
+ audio: "audio";
169
+ hdmi: "hdmi";
170
+ uvc_h264: "uvc_h264";
171
+ uvc_h265: "uvc_h265";
172
+ mjpeg: "mjpeg";
173
+ camlink: "camlink";
174
+ test: "test";
175
+ network: "network";
176
+ }>>;
160
177
  }, z.core.$strip>;
161
178
  export type CerastreamConfig = z.infer<typeof cerastreamConfigSchema>;
162
179
  export type PartialCerastreamConfig = z.input<typeof cerastreamConfigSchema>;
@@ -173,6 +190,26 @@ export declare const captureCapSchema: z.ZodObject<{
173
190
  media_type: z.ZodOptional<z.ZodString>;
174
191
  }, z.core.$strip>;
175
192
  export type CaptureCap = z.infer<typeof captureCapSchema>;
193
+ export declare const captureModeSchema: z.ZodObject<{
194
+ media_type: z.ZodString;
195
+ pipeline_kind: z.ZodEnum<{
196
+ audio: "audio";
197
+ hdmi: "hdmi";
198
+ uvc_h264: "uvc_h264";
199
+ uvc_h265: "uvc_h265";
200
+ mjpeg: "mjpeg";
201
+ camlink: "camlink";
202
+ test: "test";
203
+ network: "network";
204
+ }>;
205
+ caps: z.ZodArray<z.ZodObject<{
206
+ width: z.ZodOptional<z.ZodNumber>;
207
+ height: z.ZodOptional<z.ZodNumber>;
208
+ framerate: z.ZodOptional<z.ZodString>;
209
+ media_type: z.ZodOptional<z.ZodString>;
210
+ }, z.core.$strip>>;
211
+ }, z.core.$strip>;
212
+ export type CaptureMode = z.infer<typeof captureModeSchema>;
176
213
  export declare const captureDeviceSchema: z.ZodObject<{
177
214
  input_id: z.ZodString;
178
215
  device_path: z.ZodString;
@@ -197,6 +234,25 @@ export declare const captureDeviceSchema: z.ZodObject<{
197
234
  test: "test";
198
235
  network: "network";
199
236
  }>>;
237
+ modes: z.ZodOptional<z.ZodArray<z.ZodObject<{
238
+ media_type: z.ZodString;
239
+ pipeline_kind: z.ZodEnum<{
240
+ audio: "audio";
241
+ hdmi: "hdmi";
242
+ uvc_h264: "uvc_h264";
243
+ uvc_h265: "uvc_h265";
244
+ mjpeg: "mjpeg";
245
+ camlink: "camlink";
246
+ test: "test";
247
+ network: "network";
248
+ }>;
249
+ caps: z.ZodArray<z.ZodObject<{
250
+ width: z.ZodOptional<z.ZodNumber>;
251
+ height: z.ZodOptional<z.ZodNumber>;
252
+ framerate: z.ZodOptional<z.ZodString>;
253
+ media_type: z.ZodOptional<z.ZodString>;
254
+ }, z.core.$strip>>;
255
+ }, z.core.$strip>>>;
200
256
  alsa_card_id: z.ZodOptional<z.ZodString>;
201
257
  product_name: z.ZodOptional<z.ZodString>;
202
258
  transport: z.ZodOptional<z.ZodEnum<{
@@ -206,6 +262,8 @@ export declare const captureDeviceSchema: z.ZodObject<{
206
262
  onboard: "onboard";
207
263
  }>>;
208
264
  stable_id: z.ZodOptional<z.ZodString>;
265
+ physical_group_id: z.ZodOptional<z.ZodString>;
266
+ hardware_serial: z.ZodOptional<z.ZodString>;
209
267
  }, z.core.$strip>;
210
268
  export type CaptureDevice = z.infer<typeof captureDeviceSchema>;
211
269
  export declare const srtStatsSchema: z.ZodObject<{
package/dist/types.js CHANGED
@@ -52,6 +52,18 @@ export const deviceTransportSchema = z.enum([
52
52
  "bluetooth",
53
53
  "onboard",
54
54
  ]);
55
+ // Phase of one `change-config` transaction (0.10.0, additive). `applying` is
56
+ // published once at entry, then exactly ONE terminal phase for the same
57
+ // attempt_id: `applied` (the new config satisfied the outcome gate),
58
+ // `reverted` (it did not and the single known-good rollback attempt did), or
59
+ // `rollback_failed` (no rollback was possible, or the one attempt also failed —
60
+ // the engine is idle, which is the truthful terminal state).
61
+ export const configChangePhaseSchema = z.enum([
62
+ "applying",
63
+ "applied",
64
+ "reverted",
65
+ "rollback_failed",
66
+ ]);
55
67
  // ---- config sub-schemas (canonical; reused by `start` + the unified config) ----
56
68
  /** SRT transport config. Mirrors schema.md `start.srt` exactly. */
57
69
  export const srtConfigSchema = z.object({
@@ -117,6 +129,7 @@ export const cerastreamConfigSchema = z.object({
117
129
  framerate: z.number().optional(), // additive (0.4.0): fps as a number, e.g. 29.97
118
130
  audio: audioConfigSchema.optional(), // additive (0.4.0): audio device/codec/signed delay
119
131
  video_passthrough: videoPassthroughSchema.optional(), // additive (0.5.0): auto|force|off; absent ⇒ auto
132
+ input_mode: captureDeviceKindSchema.optional(), // additive (0.11.0): which MODE family of the selected device to capture in, naming a list-devices modes[].pipeline_kind. Absent ⇒ the engine's precedence pick (h265 → h264 → mjpeg → raw). The ONLY way to reach the non-precedence family of a dual-format camera; a family the device does not expose is a typed cerastream.params.invalid, never a silent fallback
120
133
  });
121
134
  // convenience defaults for building a config client-side
122
135
  export const DEFAULT_BITRATE_CONFIG = {
@@ -132,17 +145,39 @@ export const captureCapSchema = z.object({
132
145
  framerate: z.string().optional(), // e.g. "30/1"
133
146
  media_type: z.string().optional(), // e.g. "video/x-h265"; additive, absent on legacy producers
134
147
  });
148
+ // One startable capture MODE family of a device (0.11.0, additive).
149
+ //
150
+ // A capture device is not necessarily one format family: the DJI Osmo Pocket 3
151
+ // exposes H.264 AND MJPEG on the SAME /dev/video1 node (selection is a
152
+ // VIDIOC_S_FMT pixelformat choice, not a different node). The scalar
153
+ // captureDevice.kind can only name ONE family, so before this contract a
154
+ // dual-format camera was classified uvc_h264 by precedence and its MJPEG ladder
155
+ // was unreachable — visible, but impossible to start.
156
+ //
157
+ // pipeline_kind is exactly what a caller echoes back as start.input_mode to drive
158
+ // the device in this family. caps is the ladder for THIS family ALONE, never the
159
+ // device-wide union: on the Osmo, H.264 offers 4K at 60/50/48 fps while MJPEG
160
+ // tops out at 30, so a merged list would advertise 4K60 MJPEG — a mode the device
161
+ // cannot deliver.
162
+ export const captureModeSchema = z.object({
163
+ media_type: z.string(), // e.g. "image/jpeg"
164
+ pipeline_kind: captureDeviceKindSchema, // the value start.input_mode takes to select this mode
165
+ caps: z.array(captureCapSchema), // the {w × h × framerate} ladder for THIS media type only
166
+ });
135
167
  export const captureDeviceSchema = z.object({
136
168
  input_id: z.string(), // stable id used by switch-input
137
169
  device_path: z.string(), // e.g. /dev/video0 (dedup key)
138
170
  display_name: z.string(),
139
171
  media_class: mediaClassSchema,
140
172
  caps: z.array(captureCapSchema).optional(),
141
- kind: captureDeviceKindSchema.optional(), // additive (0.4.0): engine-typed device family; absent on legacy producers
173
+ kind: captureDeviceKindSchema.optional(), // additive (0.4.0): engine-typed device family; absent on legacy producers. A dual-format device reports its HIGHEST-PRECEDENCE family here; the full set is `modes`
174
+ modes: z.array(captureModeSchema).optional(), // additive (0.11.0): every startable mode family, each with its OWN caps ladder; absent on legacy producers, on audio rows, and on a device with no probed caps. When present it ALWAYS contains the family named by `kind`, first
142
175
  alsa_card_id: z.string().optional(), // additive: ALSA card id for media_class:audio devices only; absent on video + legacy producers
143
176
  product_name: z.string().optional(), // additive (Todo 20): real product name, deduped with a #N suffix when shared; absent ⇒ use display_name
144
177
  transport: deviceTransportSchema.optional(), // additive (Todo 20): how the device is attached; absent on legacy producers
145
178
  stable_id: z.string().optional(), // additive (Todo 20): reboot-stable hardware identity, distinct from input_id/device_path
179
+ physical_group_id: z.string().optional(), // additive (0.10.0, ADR-0008): `usb:<topology-token>` shared by one physical device's rows; absent on non-USB + legacy producers, and an absent group NEVER matches
180
+ hardware_serial: z.string().optional(), // additive (0.10.0, ADR-0008): USB serial, DIAGNOSTIC ONLY — vendors ship placeholder serials, so never match/group/select on it
146
181
  });
147
182
  // ---- transport telemetry (srt-stats event payload) ----
148
183
  export const srtStatsSchema = z.object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ceralive/cerastream",
3
- "version": "2026.7.3",
3
+ "version": "2026.7.5",
4
4
  "description": "Type-safe TypeScript schema + IPC client for the cerastream streaming engine (JSON-RPC 2.0 / NDJSON over a Unix domain socket).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",