@ceralive/cerastream 2026.8.0 → 2026.9.0

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
@@ -8,6 +8,7 @@ engine's JSON-RPC 2.0 / NDJSON control plane over a Unix domain socket.
8
8
  - The eight v1 control methods (Zod params + result schemas + inferred types)
9
9
  - The nine server-push event payloads (discriminated union)
10
10
  - Two-tier error codes (RPC + runtime)
11
+ - Typed capture-probe causes retained on runtime events and rejected-start exceptions
11
12
  - A unified engine config schema (= `start` params)
12
13
  - A `CerastreamClient` interface + `connect()` factory (UDS transport)
13
14
  - Additive `client.getCapabilities()` discovery for platform, source, encoder, and
package/dist/client.js CHANGED
@@ -183,7 +183,7 @@ class ClientImpl {
183
183
  if (!parsed.success)
184
184
  return;
185
185
  const { id, error } = parsed.data;
186
- const rpcErr = new CerastreamRpcError(error.code, error.message, error.data?.code, id);
186
+ const rpcErr = new CerastreamRpcError(error.code, error.message, error.data?.code, id, error.data);
187
187
  if (id == null)
188
188
  return; // parse error with null id — no request to settle.
189
189
  const pending = this.pending.get(id);
@@ -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.11.0';
11
+ export declare const SCHEMA_VERSION: '0.12.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}. */
@@ -56,14 +56,27 @@ export declare const SUPPORTED_PROFILES: readonly ['balanced', 'low-latency', 'r
56
56
  * set or any preset's meaning changes. Mirrors the Rust `PROFILE_CATALOG_VERSION`.
57
57
  */
58
58
  export declare const PROFILE_CATALOG_VERSION: '1.0.0';
59
+ /**
60
+ * PipeWire-backed audio capture: the engine addresses capture nodes by
61
+ * `node.name`/`object.serial`, so a source with no ALSA card — a Bluetooth
62
+ * microphone is the first one — is selectable as an ordinary node. Unlike the
63
+ * other tokens this one describes the engine's RUNNING configuration, so an
64
+ * engine configured for ALSA omits it even though this catalog lists it. Mirrors
65
+ * the Rust `PIPEWIRE_CAPTURE_FEATURE`.
66
+ */
67
+ export declare const PIPEWIRE_CAPTURE_FEATURE: 'pipewire-capture';
59
68
  /**
60
69
  * Named engine features `get-capabilities` advertises in `features`. This is the
61
70
  * fail-closed negotiation contract: CeraUI sends a new out-of-schema field ONLY
62
71
  * when its feature is listed, so an engine that predates a feature (and silently
63
72
  * ignores the field) never applies a semantic the caller assumed. Mirrors the
64
73
  * Rust `ENGINE_FEATURES`.
74
+ *
75
+ * This is the full CATALOG. Gate on the token in the engine's own `features`
76
+ * array, never on membership here — a configuration-dependent token is filtered
77
+ * out of the response the engine actually sends.
65
78
  */
66
- export declare const ENGINE_FEATURES: readonly ['video-passthrough', 'input-mode', 'audio-pcm-spec'];
79
+ export declare const ENGINE_FEATURES: readonly ['video-passthrough', 'input-mode', 'audio-pcm-spec', "pipewire-capture"];
67
80
  /** Engine binary name (systemd-owned; CeraUI never spawns it — ADR-0005). */
68
81
  export declare const CERASTREAM_BIN: 'cerastream';
69
82
  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.11.0';
14
+ export const SCHEMA_VERSION = '0.12.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}. */
@@ -65,14 +65,32 @@ export const SUPPORTED_PROFILES = [
65
65
  * set or any preset's meaning changes. Mirrors the Rust `PROFILE_CATALOG_VERSION`.
66
66
  */
67
67
  export const PROFILE_CATALOG_VERSION = '1.0.0';
68
+ /**
69
+ * PipeWire-backed audio capture: the engine addresses capture nodes by
70
+ * `node.name`/`object.serial`, so a source with no ALSA card — a Bluetooth
71
+ * microphone is the first one — is selectable as an ordinary node. Unlike the
72
+ * other tokens this one describes the engine's RUNNING configuration, so an
73
+ * engine configured for ALSA omits it even though this catalog lists it. Mirrors
74
+ * the Rust `PIPEWIRE_CAPTURE_FEATURE`.
75
+ */
76
+ export const PIPEWIRE_CAPTURE_FEATURE = 'pipewire-capture';
68
77
  /**
69
78
  * Named engine features `get-capabilities` advertises in `features`. This is the
70
79
  * fail-closed negotiation contract: CeraUI sends a new out-of-schema field ONLY
71
80
  * when its feature is listed, so an engine that predates a feature (and silently
72
81
  * ignores the field) never applies a semantic the caller assumed. Mirrors the
73
82
  * Rust `ENGINE_FEATURES`.
83
+ *
84
+ * This is the full CATALOG. Gate on the token in the engine's own `features`
85
+ * array, never on membership here — a configuration-dependent token is filtered
86
+ * out of the response the engine actually sends.
74
87
  */
75
- export const ENGINE_FEATURES = ['video-passthrough', 'input-mode', 'audio-pcm-spec'];
88
+ export const ENGINE_FEATURES = [
89
+ 'video-passthrough',
90
+ 'input-mode',
91
+ 'audio-pcm-spec',
92
+ PIPEWIRE_CAPTURE_FEATURE,
93
+ ];
76
94
  /** Engine binary name (systemd-owned; CeraUI never spawns it — ADR-0005). */
77
95
  export const CERASTREAM_BIN = 'cerastream';
78
96
  // ---- config defaults (mirror ceracoder, the engine being replaced) ----
package/dist/errors.d.ts CHANGED
@@ -20,7 +20,11 @@ export declare class CerastreamRpcError extends Error {
20
20
  readonly dataCode: string | undefined;
21
21
  /** The request id this error answered, when present. */
22
22
  readonly requestId: number | string | null;
23
- constructor(code: number, message: string, dataCode: string | undefined, requestId: number | string | null);
23
+ /** Full JSON-RPC `error.data` object, retained for additive typed detail. */
24
+ readonly data?: unknown;
25
+ constructor(code: number, message: string, dataCode: string | undefined, requestId: number | string | null, data?: unknown);
26
+ /** Typed capture-probe failures carried by a rejected stream start. */
27
+ captureCauses(): readonly CaptureCauseEntry[];
24
28
  }
25
29
  /**
26
30
  * Machine-readable classification of a {@link CerastreamConnectionError}, stable
@@ -91,6 +95,21 @@ export declare const processErrorCodeSchema: z.ZodEnum<{
91
95
  srtla_no_connections: "srtla_no_connections";
92
96
  }>;
93
97
  export type ProcessErrorCode = z.infer<typeof processErrorCodeSchema>;
98
+ export declare const captureCauseSchema: z.ZodEnum<{
99
+ device_busy: "device_busy";
100
+ negotiation_failed: "negotiation_failed";
101
+ no_signal: "no_signal";
102
+ }>;
103
+ export type CaptureCause = z.infer<typeof captureCauseSchema>;
104
+ export declare const captureCauseEntrySchema: z.ZodObject<{
105
+ device: z.ZodString;
106
+ cause: z.ZodEnum<{
107
+ device_busy: "device_busy";
108
+ negotiation_failed: "negotiation_failed";
109
+ no_signal: "no_signal";
110
+ }>;
111
+ }, z.core.$strip>;
112
+ export type CaptureCauseEntry = z.infer<typeof captureCauseEntrySchema>;
94
113
  /**
95
114
  * Why a capture device became `capture_unrecoverable` — the `reason` field of the
96
115
  * `error` event. Mirrors the Rust `CaptureUnrecoverableReason`.
package/dist/errors.js CHANGED
@@ -23,12 +23,21 @@ export class CerastreamRpcError extends Error {
23
23
  dataCode;
24
24
  /** The request id this error answered, when present. */
25
25
  requestId;
26
- constructor(code, message, dataCode, requestId) {
26
+ /** Full JSON-RPC `error.data` object, retained for additive typed detail. */
27
+ data;
28
+ constructor(code, message, dataCode, requestId, data) {
27
29
  super(message);
28
30
  this.name = 'CerastreamRpcError';
29
31
  this.code = code;
30
32
  this.dataCode = dataCode;
31
33
  this.requestId = requestId;
34
+ if (data !== undefined)
35
+ this.data = data;
36
+ }
37
+ /** Typed capture-probe failures carried by a rejected stream start. */
38
+ captureCauses() {
39
+ const parsed = rpcCaptureDataSchema.safeParse(this.data);
40
+ return parsed.success ? parsed.data.capture_causes : [];
32
41
  }
33
42
  }
34
43
  /**
@@ -129,6 +138,16 @@ export const processErrorCodeSchema = z.enum([
129
138
  // would be lying. Always carries a captureUnrecoverableReasonSchema `reason`.
130
139
  'capture_unrecoverable',
131
140
  ]);
141
+ export const captureCauseSchema = z.enum(['negotiation_failed', 'no_signal', 'device_busy']);
142
+ export const captureCauseEntrySchema = z.object({
143
+ device: z.string(),
144
+ cause: captureCauseSchema,
145
+ });
146
+ const rpcCaptureDataSchema = z
147
+ .object({
148
+ capture_causes: z.array(captureCauseEntrySchema),
149
+ })
150
+ .passthrough();
132
151
  /**
133
152
  * Why a capture device became `capture_unrecoverable` — the `reason` field of the
134
153
  * `error` event. Mirrors the Rust `CaptureUnrecoverableReason`.
package/dist/events.d.ts CHANGED
@@ -192,6 +192,7 @@ export declare const deviceEventSchema: z.ZodObject<{
192
192
  stable_id: z.ZodOptional<z.ZodString>;
193
193
  physical_group_id: z.ZodOptional<z.ZodString>;
194
194
  hardware_serial: z.ZodOptional<z.ZodString>;
195
+ device_address: z.ZodOptional<z.ZodString>;
195
196
  }, z.core.$strip>;
196
197
  }, z.core.$strip>;
197
198
  /** Payload of a {@link deviceEventSchema} event. */
@@ -238,6 +239,11 @@ export declare const runtimeErrorEventSchema: z.ZodObject<{
238
239
  }>;
239
240
  reason: z.ZodOptional<z.ZodString>;
240
241
  selected: z.ZodOptional<z.ZodBoolean>;
242
+ capture_cause: z.ZodOptional<z.ZodEnum<{
243
+ device_busy: "device_busy";
244
+ negotiation_failed: "negotiation_failed";
245
+ no_signal: "no_signal";
246
+ }>>;
241
247
  }, z.core.$strip>;
242
248
  /** Payload of a {@link runtimeErrorEventSchema} event. */
243
249
  export type RuntimeErrorEvent = z.infer<typeof runtimeErrorEventSchema>;
@@ -424,6 +430,7 @@ export declare const eventParamsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
424
430
  stable_id: z.ZodOptional<z.ZodString>;
425
431
  physical_group_id: z.ZodOptional<z.ZodString>;
426
432
  hardware_serial: z.ZodOptional<z.ZodString>;
433
+ device_address: z.ZodOptional<z.ZodString>;
427
434
  }, z.core.$strip>;
428
435
  }, z.core.$strip>, z.ZodObject<{
429
436
  type: z.ZodLiteral<"bitrate">;
@@ -455,6 +462,11 @@ export declare const eventParamsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
455
462
  }>;
456
463
  reason: z.ZodOptional<z.ZodString>;
457
464
  selected: z.ZodOptional<z.ZodBoolean>;
465
+ capture_cause: z.ZodOptional<z.ZodEnum<{
466
+ device_busy: "device_busy";
467
+ negotiation_failed: "negotiation_failed";
468
+ no_signal: "no_signal";
469
+ }>>;
458
470
  }, z.core.$strip>, z.ZodObject<{
459
471
  type: z.ZodLiteral<"preview">;
460
472
  seq: z.ZodNumber;
@@ -615,6 +627,7 @@ export declare const cerastreamEventSchema: z.ZodObject<{
615
627
  stable_id: z.ZodOptional<z.ZodString>;
616
628
  physical_group_id: z.ZodOptional<z.ZodString>;
617
629
  hardware_serial: z.ZodOptional<z.ZodString>;
630
+ device_address: z.ZodOptional<z.ZodString>;
618
631
  }, z.core.$strip>;
619
632
  }, z.core.$strip>, z.ZodObject<{
620
633
  type: z.ZodLiteral<"bitrate">;
@@ -646,6 +659,11 @@ export declare const cerastreamEventSchema: z.ZodObject<{
646
659
  }>;
647
660
  reason: z.ZodOptional<z.ZodString>;
648
661
  selected: z.ZodOptional<z.ZodBoolean>;
662
+ capture_cause: z.ZodOptional<z.ZodEnum<{
663
+ device_busy: "device_busy";
664
+ negotiation_failed: "negotiation_failed";
665
+ no_signal: "no_signal";
666
+ }>>;
649
667
  }, z.core.$strip>, z.ZodObject<{
650
668
  type: z.ZodLiteral<"preview">;
651
669
  seq: z.ZodNumber;
@@ -812,6 +830,7 @@ export declare const eventSchemas: {
812
830
  stable_id: z.ZodOptional<z.ZodString>;
813
831
  physical_group_id: z.ZodOptional<z.ZodString>;
814
832
  hardware_serial: z.ZodOptional<z.ZodString>;
833
+ device_address: z.ZodOptional<z.ZodString>;
815
834
  }, z.core.$strip>;
816
835
  }, z.core.$strip>;
817
836
  readonly bitrate: z.ZodObject<{
@@ -846,6 +865,11 @@ export declare const eventSchemas: {
846
865
  }>;
847
866
  reason: z.ZodOptional<z.ZodString>;
848
867
  selected: z.ZodOptional<z.ZodBoolean>;
868
+ capture_cause: z.ZodOptional<z.ZodEnum<{
869
+ device_busy: "device_busy";
870
+ negotiation_failed: "negotiation_failed";
871
+ no_signal: "no_signal";
872
+ }>>;
849
873
  }, z.core.$strip>;
850
874
  readonly preview: z.ZodObject<{
851
875
  type: z.ZodLiteral<"preview">;
package/dist/events.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { processErrorCodeSchema, processErrorSourceSchema } from './errors.js';
2
+ import { captureCauseSchema, processErrorCodeSchema, processErrorSourceSchema } from './errors.js';
3
3
  import { captureDeviceSchema, configChangePhaseSchema, inputModeSchema, mediaClassSchema, previewEncodeFallbackSchema, previewEncodeModeSchema, 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:
@@ -119,6 +119,8 @@ export const runtimeErrorEventSchema = z.object({
119
119
  source: processErrorSourceSchema,
120
120
  reason: z.string().optional(), // structured replacement for the stderr <reason>
121
121
  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
122
+ // The code stays capture_video_error because older consumers reject unknown code values.
123
+ capture_cause: captureCauseSchema.optional(),
122
124
  });
123
125
  /** `preview` event — a preview-session lifecycle/phase change. */
124
126
  export const previewEventSchema = z.object({
@@ -241,6 +241,7 @@ export declare const listDevicesResultSchema: z.ZodObject<{
241
241
  stable_id: z.ZodOptional<z.ZodString>;
242
242
  physical_group_id: z.ZodOptional<z.ZodString>;
243
243
  hardware_serial: z.ZodOptional<z.ZodString>;
244
+ device_address: z.ZodOptional<z.ZodString>;
244
245
  }, z.core.$strip>>;
245
246
  }, z.core.$strip>;
246
247
  export type ListDevicesResult = z.infer<typeof listDevicesResultSchema>;
@@ -711,6 +712,7 @@ export declare const requestSchemas: {
711
712
  stable_id: z.ZodOptional<z.ZodString>;
712
713
  physical_group_id: z.ZodOptional<z.ZodString>;
713
714
  hardware_serial: z.ZodOptional<z.ZodString>;
715
+ device_address: z.ZodOptional<z.ZodString>;
714
716
  }, z.core.$strip>>;
715
717
  }, z.core.$strip>;
716
718
  };
package/dist/types.d.ts CHANGED
@@ -276,6 +276,7 @@ export declare const captureDeviceSchema: z.ZodObject<{
276
276
  stable_id: z.ZodOptional<z.ZodString>;
277
277
  physical_group_id: z.ZodOptional<z.ZodString>;
278
278
  hardware_serial: z.ZodOptional<z.ZodString>;
279
+ device_address: z.ZodOptional<z.ZodString>;
279
280
  }, z.core.$strip>;
280
281
  export type CaptureDevice = z.infer<typeof captureDeviceSchema>;
281
282
  export declare const srtStatsSchema: z.ZodObject<{
package/dist/types.js CHANGED
@@ -184,6 +184,7 @@ export const captureDeviceSchema = z.object({
184
184
  stable_id: z.string().optional(), // additive (Todo 20): reboot-stable hardware identity, distinct from input_id/device_path
185
185
  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
186
186
  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
187
+ device_address: z.string().optional(), // additive: the address the node's provider advertises, carried verbatim; the engine attaches no meaning to it, so the consumer owning the address space is the one that matches on it. Absent unless a provider published one
187
188
  });
188
189
  // ---- transport telemetry (srt-stats event payload) ----
189
190
  export const srtStatsSchema = z.object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ceralive/cerastream",
3
- "version": "2026.8.0",
3
+ "version": "2026.9.0",
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",