@ceralive/cerastream 2026.7.4 → 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.
@@ -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.10.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.10.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<{
@@ -164,12 +183,14 @@ export declare const runtimeErrorEventSchema: z.ZodObject<{
164
183
  pipeline_stall: "pipeline_stall";
165
184
  srt_connect_failed: "srt_connect_failed";
166
185
  srt_connection_lost: "srt_connection_lost";
186
+ capture_unrecoverable: "capture_unrecoverable";
167
187
  }>;
168
188
  source: z.ZodEnum<{
169
189
  srtla: "srtla";
170
190
  engine: "engine";
171
191
  }>;
172
192
  reason: z.ZodOptional<z.ZodString>;
193
+ selected: z.ZodOptional<z.ZodBoolean>;
173
194
  }, z.core.$strip>;
174
195
  /** Payload of a {@link runtimeErrorEventSchema} event. */
175
196
  export type RuntimeErrorEvent = z.infer<typeof runtimeErrorEventSchema>;
@@ -312,6 +333,25 @@ export declare const eventParamsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
312
333
  test: "test";
313
334
  network: "network";
314
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>>>;
315
355
  alsa_card_id: z.ZodOptional<z.ZodString>;
316
356
  product_name: z.ZodOptional<z.ZodString>;
317
357
  transport: z.ZodOptional<z.ZodEnum<{
@@ -346,12 +386,14 @@ export declare const eventParamsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
346
386
  pipeline_stall: "pipeline_stall";
347
387
  srt_connect_failed: "srt_connect_failed";
348
388
  srt_connection_lost: "srt_connection_lost";
389
+ capture_unrecoverable: "capture_unrecoverable";
349
390
  }>;
350
391
  source: z.ZodEnum<{
351
392
  srtla: "srtla";
352
393
  engine: "engine";
353
394
  }>;
354
395
  reason: z.ZodOptional<z.ZodString>;
396
+ selected: z.ZodOptional<z.ZodBoolean>;
355
397
  }, z.core.$strip>, z.ZodObject<{
356
398
  type: z.ZodLiteral<"preview">;
357
399
  seq: z.ZodNumber;
@@ -468,6 +510,25 @@ export declare const cerastreamEventSchema: z.ZodObject<{
468
510
  test: "test";
469
511
  network: "network";
470
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>>>;
471
532
  alsa_card_id: z.ZodOptional<z.ZodString>;
472
533
  product_name: z.ZodOptional<z.ZodString>;
473
534
  transport: z.ZodOptional<z.ZodEnum<{
@@ -502,12 +563,14 @@ export declare const cerastreamEventSchema: z.ZodObject<{
502
563
  pipeline_stall: "pipeline_stall";
503
564
  srt_connect_failed: "srt_connect_failed";
504
565
  srt_connection_lost: "srt_connection_lost";
566
+ capture_unrecoverable: "capture_unrecoverable";
505
567
  }>;
506
568
  source: z.ZodEnum<{
507
569
  srtla: "srtla";
508
570
  engine: "engine";
509
571
  }>;
510
572
  reason: z.ZodOptional<z.ZodString>;
573
+ selected: z.ZodOptional<z.ZodBoolean>;
511
574
  }, z.core.$strip>, z.ZodObject<{
512
575
  type: z.ZodLiteral<"preview">;
513
576
  seq: z.ZodNumber;
@@ -630,6 +693,25 @@ export declare const eventSchemas: {
630
693
  test: "test";
631
694
  network: "network";
632
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>>>;
633
715
  alsa_card_id: z.ZodOptional<z.ZodString>;
634
716
  product_name: z.ZodOptional<z.ZodString>;
635
717
  transport: z.ZodOptional<z.ZodEnum<{
@@ -667,12 +749,14 @@ export declare const eventSchemas: {
667
749
  pipeline_stall: "pipeline_stall";
668
750
  srt_connect_failed: "srt_connect_failed";
669
751
  srt_connection_lost: "srt_connection_lost";
752
+ capture_unrecoverable: "capture_unrecoverable";
670
753
  }>;
671
754
  source: z.ZodEnum<{
672
755
  srtla: "srtla";
673
756
  engine: "engine";
674
757
  }>;
675
758
  reason: z.ZodOptional<z.ZodString>;
759
+ selected: z.ZodOptional<z.ZodBoolean>;
676
760
  }, z.core.$strip>;
677
761
  readonly preview: z.ZodObject<{
678
762
  type: z.ZodLiteral<"preview">;
package/dist/events.js CHANGED
@@ -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({
@@ -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<{
@@ -193,6 +203,25 @@ export declare const listDevicesResultSchema: z.ZodObject<{
193
203
  test: "test";
194
204
  network: "network";
195
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>>>;
196
225
  alsa_card_id: z.ZodOptional<z.ZodString>;
197
226
  product_name: z.ZodOptional<z.ZodString>;
198
227
  transport: z.ZodOptional<z.ZodEnum<{
@@ -389,6 +418,16 @@ export declare const changeConfigParamsSchema: z.ZodObject<{
389
418
  h265: "h265";
390
419
  }>>;
391
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
+ }>>;
392
431
  }, z.core.$strip>;
393
432
  export type ChangeConfigParams = z.infer<typeof changeConfigParamsSchema>;
394
433
  export declare const changeConfigResultSchema: z.ZodObject<{
@@ -480,6 +519,16 @@ export declare const requestSchemas: {
480
519
  force: "force";
481
520
  off: "off";
482
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
+ }>>;
483
532
  }, z.core.$strip>;
484
533
  readonly result: z.ZodObject<{
485
534
  session_id: z.ZodString;
@@ -614,6 +663,25 @@ export declare const requestSchemas: {
614
663
  test: "test";
615
664
  network: "network";
616
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>>>;
617
685
  alsa_card_id: z.ZodOptional<z.ZodString>;
618
686
  product_name: z.ZodOptional<z.ZodString>;
619
687
  transport: z.ZodOptional<z.ZodEnum<{
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, configChangePhaseSchema, inputModeSchema, mediaClassSchema, previewTierSchema, streamStateSchema, videoCodecSchema, } 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 ----
@@ -207,6 +207,13 @@ export const changeConfigParamsSchema = z
207
207
  framerate: z.number().optional(),
208
208
  codec: videoCodecSchema.optional(),
209
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(),
210
217
  })
211
218
  .refine((delta) => Object.keys(delta).length > 0, {
212
219
  message: "change-config delta must carry at least one field",
package/dist/types.d.ts CHANGED
@@ -164,6 +164,16 @@ export declare const cerastreamConfigSchema: z.ZodObject<{
164
164
  force: "force";
165
165
  off: "off";
166
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
+ }>>;
167
177
  }, z.core.$strip>;
168
178
  export type CerastreamConfig = z.infer<typeof cerastreamConfigSchema>;
169
179
  export type PartialCerastreamConfig = z.input<typeof cerastreamConfigSchema>;
@@ -180,6 +190,26 @@ export declare const captureCapSchema: z.ZodObject<{
180
190
  media_type: z.ZodOptional<z.ZodString>;
181
191
  }, z.core.$strip>;
182
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>;
183
213
  export declare const captureDeviceSchema: z.ZodObject<{
184
214
  input_id: z.ZodString;
185
215
  device_path: z.ZodString;
@@ -204,6 +234,25 @@ export declare const captureDeviceSchema: z.ZodObject<{
204
234
  test: "test";
205
235
  network: "network";
206
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>>>;
207
256
  alsa_card_id: z.ZodOptional<z.ZodString>;
208
257
  product_name: z.ZodOptional<z.ZodString>;
209
258
  transport: z.ZodOptional<z.ZodEnum<{
package/dist/types.js CHANGED
@@ -129,6 +129,7 @@ export const cerastreamConfigSchema = z.object({
129
129
  framerate: z.number().optional(), // additive (0.4.0): fps as a number, e.g. 29.97
130
130
  audio: audioConfigSchema.optional(), // additive (0.4.0): audio device/codec/signed delay
131
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
132
133
  });
133
134
  // convenience defaults for building a config client-side
134
135
  export const DEFAULT_BITRATE_CONFIG = {
@@ -144,13 +145,33 @@ export const captureCapSchema = z.object({
144
145
  framerate: z.string().optional(), // e.g. "30/1"
145
146
  media_type: z.string().optional(), // e.g. "video/x-h265"; additive, absent on legacy producers
146
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
+ });
147
167
  export const captureDeviceSchema = z.object({
148
168
  input_id: z.string(), // stable id used by switch-input
149
169
  device_path: z.string(), // e.g. /dev/video0 (dedup key)
150
170
  display_name: z.string(),
151
171
  media_class: mediaClassSchema,
152
172
  caps: z.array(captureCapSchema).optional(),
153
- 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
154
175
  alsa_card_id: z.string().optional(), // additive: ALSA card id for media_class:audio devices only; absent on video + legacy producers
155
176
  product_name: z.string().optional(), // additive (Todo 20): real product name, deduped with a #N suffix when shared; absent ⇒ use display_name
156
177
  transport: deviceTransportSchema.optional(), // additive (Todo 20): how the device is attached; absent on legacy producers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ceralive/cerastream",
3
- "version": "2026.7.4",
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",