@ceralive/cerastream 2026.7.3 → 2026.7.4

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.10.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}. */
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.10.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}. */
package/dist/events.d.ts CHANGED
@@ -124,6 +124,8 @@ export declare const deviceEventSchema: z.ZodObject<{
124
124
  onboard: "onboard";
125
125
  }>>;
126
126
  stable_id: z.ZodOptional<z.ZodString>;
127
+ physical_group_id: z.ZodOptional<z.ZodString>;
128
+ hardware_serial: z.ZodOptional<z.ZodString>;
127
129
  }, z.core.$strip>;
128
130
  }, z.core.$strip>;
129
131
  /** Payload of a {@link deviceEventSchema} event. */
@@ -214,6 +216,28 @@ export declare const audioLevelEventSchema: z.ZodObject<{
214
216
  }, z.core.$strip>;
215
217
  /** Payload of a {@link audioLevelEventSchema} event. */
216
218
  export type AudioLevelEvent = z.infer<typeof audioLevelEventSchema>;
219
+ /**
220
+ * `config-change` event — one phase of a `change-config` transaction (0.10.0,
221
+ * additive Todo 9). Exactly one `applying` at entry, then exactly ONE terminal
222
+ * phase for the same `attempt_id` (which the RPC result also echoes, so a caller
223
+ * that missed an event can still correlate). A terminal phase is published only
224
+ * after the outcome gate resolved, so `applied` means "PLAYING **and** frames
225
+ * actually advancing", never merely "PLAYING".
226
+ */
227
+ export declare const configChangeEventSchema: z.ZodObject<{
228
+ type: z.ZodLiteral<"config-change">;
229
+ seq: z.ZodNumber;
230
+ attempt_id: z.ZodString;
231
+ phase: z.ZodEnum<{
232
+ applying: "applying";
233
+ applied: "applied";
234
+ reverted: "reverted";
235
+ rollback_failed: "rollback_failed";
236
+ }>;
237
+ reason: z.ZodOptional<z.ZodString>;
238
+ }, z.core.$strip>;
239
+ /** Payload of a {@link configChangeEventSchema} event. */
240
+ export type ConfigChangeEvent = z.infer<typeof configChangeEventSchema>;
217
241
  /** Discriminated union of every v1 event payload (the inner `params`). */
218
242
  export declare const eventParamsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
219
243
  type: z.ZodLiteral<"status">;
@@ -297,6 +321,8 @@ export declare const eventParamsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
297
321
  onboard: "onboard";
298
322
  }>>;
299
323
  stable_id: z.ZodOptional<z.ZodString>;
324
+ physical_group_id: z.ZodOptional<z.ZodString>;
325
+ hardware_serial: z.ZodOptional<z.ZodString>;
300
326
  }, z.core.$strip>;
301
327
  }, z.core.$strip>, z.ZodObject<{
302
328
  type: z.ZodLiteral<"bitrate">;
@@ -352,6 +378,17 @@ export declare const eventParamsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
352
378
  mode_none: "mode_none";
353
379
  handoff: "handoff";
354
380
  }>>;
381
+ }, z.core.$strip>, z.ZodObject<{
382
+ type: z.ZodLiteral<"config-change">;
383
+ seq: z.ZodNumber;
384
+ attempt_id: z.ZodString;
385
+ phase: z.ZodEnum<{
386
+ applying: "applying";
387
+ applied: "applied";
388
+ reverted: "reverted";
389
+ rollback_failed: "rollback_failed";
390
+ }>;
391
+ reason: z.ZodOptional<z.ZodString>;
355
392
  }, z.core.$strip>], "type">;
356
393
  export type EventParams = z.infer<typeof eventParamsSchema>;
357
394
  /** Full event envelope: { jsonrpc, method:"event", params } with typed params. */
@@ -440,6 +477,8 @@ export declare const cerastreamEventSchema: z.ZodObject<{
440
477
  onboard: "onboard";
441
478
  }>>;
442
479
  stable_id: z.ZodOptional<z.ZodString>;
480
+ physical_group_id: z.ZodOptional<z.ZodString>;
481
+ hardware_serial: z.ZodOptional<z.ZodString>;
443
482
  }, z.core.$strip>;
444
483
  }, z.core.$strip>, z.ZodObject<{
445
484
  type: z.ZodLiteral<"bitrate">;
@@ -495,12 +534,24 @@ export declare const cerastreamEventSchema: z.ZodObject<{
495
534
  mode_none: "mode_none";
496
535
  handoff: "handoff";
497
536
  }>>;
537
+ }, z.core.$strip>, z.ZodObject<{
538
+ type: z.ZodLiteral<"config-change">;
539
+ seq: z.ZodNumber;
540
+ attempt_id: z.ZodString;
541
+ phase: z.ZodEnum<{
542
+ applying: "applying";
543
+ applied: "applied";
544
+ reverted: "reverted";
545
+ rollback_failed: "rollback_failed";
546
+ }>;
547
+ reason: z.ZodOptional<z.ZodString>;
498
548
  }, z.core.$strip>], "type">;
499
549
  }, z.core.$strip>;
500
550
  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"];
551
+ /** The nine event topics (schema.md "Events" table) — count-assertion source.
552
+ * `audio-level` (Todo 21) and `config-change` (Todo 9) are additive and appended
553
+ * last, preserving the order of every prior topic. */
554
+ export declare const EVENT_TOPICS: readonly ["status", "switch", "device", "bitrate", "srt-stats", "error", "preview", "audio-level", "config-change"];
504
555
  export type EventTopicName = (typeof EVENT_TOPICS)[number];
505
556
  /** topic → payload Zod schema. Tests assert every topic is represented here. */
506
557
  export declare const eventSchemas: {
@@ -588,6 +639,8 @@ export declare const eventSchemas: {
588
639
  onboard: "onboard";
589
640
  }>>;
590
641
  stable_id: z.ZodOptional<z.ZodString>;
642
+ physical_group_id: z.ZodOptional<z.ZodString>;
643
+ hardware_serial: z.ZodOptional<z.ZodString>;
591
644
  }, z.core.$strip>;
592
645
  }, z.core.$strip>;
593
646
  readonly bitrate: z.ZodObject<{
@@ -649,4 +702,16 @@ export declare const eventSchemas: {
649
702
  handoff: "handoff";
650
703
  }>>;
651
704
  }, z.core.$strip>;
705
+ readonly "config-change": z.ZodObject<{
706
+ type: z.ZodLiteral<"config-change">;
707
+ seq: z.ZodNumber;
708
+ attempt_id: z.ZodString;
709
+ phase: z.ZodEnum<{
710
+ applying: "applying";
711
+ applied: "applied";
712
+ reverted: "reverted";
713
+ rollback_failed: "rollback_failed";
714
+ }>;
715
+ reason: z.ZodOptional<z.ZodString>;
716
+ }, z.core.$strip>;
652
717
  };
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.
@@ -127,6 +127,21 @@ export const audioLevelEventSchema = z.object({
127
127
  unavailable: z.literal(true).optional(),
128
128
  reason: z.enum(["device_busy", "no_device", "mode_none", "handoff"]).optional(),
129
129
  });
130
+ /**
131
+ * `config-change` event — one phase of a `change-config` transaction (0.10.0,
132
+ * additive Todo 9). Exactly one `applying` at entry, then exactly ONE terminal
133
+ * phase for the same `attempt_id` (which the RPC result also echoes, so a caller
134
+ * that missed an event can still correlate). A terminal phase is published only
135
+ * after the outcome gate resolved, so `applied` means "PLAYING **and** frames
136
+ * actually advancing", never merely "PLAYING".
137
+ */
138
+ export const configChangeEventSchema = z.object({
139
+ type: z.literal("config-change"),
140
+ seq,
141
+ attempt_id: z.string(),
142
+ phase: configChangePhaseSchema,
143
+ reason: z.string().optional(), // absent on applying/applied; "teardown_timeout" marks the supervisor escalation
144
+ });
130
145
  /** Discriminated union of every v1 event payload (the inner `params`). */
131
146
  export const eventParamsSchema = z.discriminatedUnion("type", [
132
147
  statusEventSchema,
@@ -137,6 +152,7 @@ export const eventParamsSchema = z.discriminatedUnion("type", [
137
152
  runtimeErrorEventSchema,
138
153
  previewEventSchema,
139
154
  audioLevelEventSchema,
155
+ configChangeEventSchema,
140
156
  ]);
141
157
  /** Full event envelope: { jsonrpc, method:"event", params } with typed params. */
142
158
  export const cerastreamEventSchema = z.object({
@@ -144,8 +160,9 @@ export const cerastreamEventSchema = z.object({
144
160
  method: z.literal("event"),
145
161
  params: eventParamsSchema,
146
162
  });
147
- /** The eight event topics (schema.md "Events" table) — count-assertion source.
148
- * `audio-level` is additive (Todo 21) and appended last, preserving order. */
163
+ /** The nine event topics (schema.md "Events" table) — count-assertion source.
164
+ * `audio-level` (Todo 21) and `config-change` (Todo 9) are additive and appended
165
+ * last, preserving the order of every prior topic. */
149
166
  export const EVENT_TOPICS = [
150
167
  "status",
151
168
  "switch",
@@ -155,6 +172,7 @@ export const EVENT_TOPICS = [
155
172
  "error",
156
173
  "preview",
157
174
  "audio-level",
175
+ "config-change",
158
176
  ];
159
177
  /** topic → payload Zod schema. Tests assert every topic is represented here. */
160
178
  export const eventSchemas = {
@@ -166,4 +184,5 @@ export const eventSchemas = {
166
184
  error: runtimeErrorEventSchema,
167
185
  preview: previewEventSchema,
168
186
  "audio-level": audioLevelEventSchema,
187
+ "config-change": configChangeEventSchema,
169
188
  };
@@ -83,6 +83,7 @@ export declare const reloadConfigParamsSchema: z.ZodObject<{
83
83
  audio: z.ZodOptional<z.ZodObject<{
84
84
  delay_ms: z.ZodOptional<z.ZodNumber>;
85
85
  delay_ms_signed: z.ZodOptional<z.ZodNumber>;
86
+ meter_device: z.ZodOptional<z.ZodNullable<z.ZodString>>;
86
87
  }, z.core.$strip>>;
87
88
  }, z.core.$strip>;
88
89
  export type ReloadConfigParams = z.infer<typeof reloadConfigParamsSchema>;
@@ -103,6 +104,7 @@ export declare const reloadConfigResultSchema: z.ZodObject<{
103
104
  audio: z.ZodOptional<z.ZodObject<{
104
105
  delay_ms: z.ZodOptional<z.ZodNumber>;
105
106
  delay_ms_signed: z.ZodOptional<z.ZodNumber>;
107
+ meter_device: z.ZodOptional<z.ZodNullable<z.ZodString>>;
106
108
  }, z.core.$strip>>;
107
109
  }, z.core.$strip>;
108
110
  bitrate_control: z.ZodOptional<z.ZodEnum<{
@@ -200,6 +202,8 @@ export declare const listDevicesResultSchema: z.ZodObject<{
200
202
  onboard: "onboard";
201
203
  }>>;
202
204
  stable_id: z.ZodOptional<z.ZodString>;
205
+ physical_group_id: z.ZodOptional<z.ZodString>;
206
+ hardware_serial: z.ZodOptional<z.ZodString>;
203
207
  }, z.core.$strip>>;
204
208
  }, z.core.$strip>;
205
209
  export type ListDevicesResult = z.infer<typeof listDevicesResultSchema>;
@@ -212,6 +216,7 @@ export declare const eventTopicSchema: z.ZodEnum<{
212
216
  "srt-stats": "srt-stats";
213
217
  preview: "preview";
214
218
  "audio-level": "audio-level";
219
+ "config-change": "config-change";
215
220
  }>;
216
221
  export type EventTopic = z.infer<typeof eventTopicSchema>;
217
222
  export declare const subscribeEventsParamsSchema: z.ZodObject<{
@@ -224,6 +229,7 @@ export declare const subscribeEventsParamsSchema: z.ZodObject<{
224
229
  "srt-stats": "srt-stats";
225
230
  preview: "preview";
226
231
  "audio-level": "audio-level";
232
+ "config-change": "config-change";
227
233
  }>>>;
228
234
  }, z.core.$strip>;
229
235
  export type SubscribeEventsParams = z.infer<typeof subscribeEventsParamsSchema>;
@@ -237,6 +243,7 @@ export declare const subscribeEventsResultSchema: z.ZodObject<{
237
243
  "srt-stats": "srt-stats";
238
244
  preview: "preview";
239
245
  "audio-level": "audio-level";
246
+ "config-change": "config-change";
240
247
  }>>;
241
248
  }, z.core.$strip>;
242
249
  export type SubscribeEventsResult = z.infer<typeof subscribeEventsResultSchema>;
@@ -373,6 +380,42 @@ export declare const getCapabilitiesResultSchema: z.ZodObject<{
373
380
  features: z.ZodOptional<z.ZodArray<z.ZodString>>;
374
381
  }, z.core.$strip>;
375
382
  export type GetCapabilitiesResult = z.infer<typeof getCapabilitiesResultSchema>;
383
+ export declare const changeConfigParamsSchema: z.ZodObject<{
384
+ pipeline: z.ZodOptional<z.ZodString>;
385
+ resolution: z.ZodOptional<z.ZodString>;
386
+ framerate: z.ZodOptional<z.ZodNumber>;
387
+ codec: z.ZodOptional<z.ZodEnum<{
388
+ h264: "h264";
389
+ h265: "h265";
390
+ }>>;
391
+ input_id: z.ZodOptional<z.ZodString>;
392
+ }, z.core.$strip>;
393
+ export type ChangeConfigParams = z.infer<typeof changeConfigParamsSchema>;
394
+ export declare const changeConfigResultSchema: z.ZodObject<{
395
+ attempt_id: z.ZodString;
396
+ phase: z.ZodEnum<{
397
+ applying: "applying";
398
+ applied: "applied";
399
+ reverted: "reverted";
400
+ rollback_failed: "rollback_failed";
401
+ }>;
402
+ state: z.ZodEnum<{
403
+ idle: "idle";
404
+ starting: "starting";
405
+ streaming: "streaming";
406
+ stopping: "stopping";
407
+ }>;
408
+ reason: z.ZodOptional<z.ZodString>;
409
+ }, z.core.$strip>;
410
+ export type ChangeConfigResult = z.infer<typeof changeConfigResultSchema>;
411
+ /**
412
+ * The `reason` a `rollback_failed` carries when a teardown deadline overran.
413
+ * A TERMINAL supervisor escalation, not an ordinary failure: the engine could not
414
+ * prove the old session released its capture devices within the bound, so it
415
+ * refuses to build a second session that would race it. Mirrors the Rust
416
+ * `REASON_TEARDOWN_TIMEOUT`; consumers render it distinctly.
417
+ */
418
+ export declare const REASON_TEARDOWN_TIMEOUT: "teardown_timeout";
376
419
  /** The eight v1 control methods (the literal JSON-RPC `method` strings). */
377
420
  export declare const V1_METHODS: readonly ["start", "stop", "reload-config", "set-bitrate", "switch-input", "list-devices", "subscribe-events", "preview-session"];
378
421
  export type V1Method = (typeof V1_METHODS)[number];
@@ -478,6 +521,7 @@ export declare const requestSchemas: {
478
521
  audio: z.ZodOptional<z.ZodObject<{
479
522
  delay_ms: z.ZodOptional<z.ZodNumber>;
480
523
  delay_ms_signed: z.ZodOptional<z.ZodNumber>;
524
+ meter_device: z.ZodOptional<z.ZodNullable<z.ZodString>>;
481
525
  }, z.core.$strip>>;
482
526
  }, z.core.$strip>;
483
527
  readonly result: z.ZodObject<{
@@ -497,6 +541,7 @@ export declare const requestSchemas: {
497
541
  audio: z.ZodOptional<z.ZodObject<{
498
542
  delay_ms: z.ZodOptional<z.ZodNumber>;
499
543
  delay_ms_signed: z.ZodOptional<z.ZodNumber>;
544
+ meter_device: z.ZodOptional<z.ZodNullable<z.ZodString>>;
500
545
  }, z.core.$strip>>;
501
546
  }, z.core.$strip>;
502
547
  bitrate_control: z.ZodOptional<z.ZodEnum<{
@@ -578,6 +623,8 @@ export declare const requestSchemas: {
578
623
  onboard: "onboard";
579
624
  }>>;
580
625
  stable_id: z.ZodOptional<z.ZodString>;
626
+ physical_group_id: z.ZodOptional<z.ZodString>;
627
+ hardware_serial: z.ZodOptional<z.ZodString>;
581
628
  }, z.core.$strip>>;
582
629
  }, z.core.$strip>;
583
630
  };
@@ -592,6 +639,7 @@ export declare const requestSchemas: {
592
639
  "srt-stats": "srt-stats";
593
640
  preview: "preview";
594
641
  "audio-level": "audio-level";
642
+ "config-change": "config-change";
595
643
  }>>>;
596
644
  }, z.core.$strip>;
597
645
  readonly result: z.ZodObject<{
@@ -604,6 +652,7 @@ export declare const requestSchemas: {
604
652
  "srt-stats": "srt-stats";
605
653
  preview: "preview";
606
654
  "audio-level": "audio-level";
655
+ "config-change": "config-change";
607
656
  }>>;
608
657
  }, z.core.$strip>;
609
658
  };
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, 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,42 @@ 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
+ })
211
+ .refine((delta) => Object.keys(delta).length > 0, {
212
+ message: "change-config delta must carry at least one field",
213
+ });
214
+ // `Ok` for every phase the transaction actually reached — INCLUDING
215
+ // `rollback_failed`, an honest terminal outcome rather than an RPC fault. A
216
+ // Tier-1 error means the transaction never started, so a caller can tell
217
+ // "nothing happened" from "something happened and here is what".
218
+ export const changeConfigResultSchema = z.object({
219
+ attempt_id: z.string(), // correlates with every config-change event of the attempt
220
+ phase: configChangePhaseSchema, // the TERMINAL phase reached
221
+ state: streamStateSchema, // streaming for applied/reverted, idle for rollback_failed
222
+ reason: z.string().optional(), // machine-stable cause on a non-applied phase
223
+ });
224
+ /**
225
+ * The `reason` a `rollback_failed` carries when a teardown deadline overran.
226
+ * A TERMINAL supervisor escalation, not an ordinary failure: the engine could not
227
+ * prove the old session released its capture devices within the bound, so it
228
+ * refuses to build a second session that would race it. Mirrors the Rust
229
+ * `REASON_TEARDOWN_TIMEOUT`; consumers render it distinctly.
230
+ */
231
+ export const REASON_TEARDOWN_TIMEOUT = "teardown_timeout";
191
232
  // ---- method registry (count-assertion source of truth) ----
192
233
  /** The eight v1 control methods (the literal JSON-RPC `method` strings). */
193
234
  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;
@@ -206,6 +213,8 @@ export declare const captureDeviceSchema: z.ZodObject<{
206
213
  onboard: "onboard";
207
214
  }>>;
208
215
  stable_id: z.ZodOptional<z.ZodString>;
216
+ physical_group_id: z.ZodOptional<z.ZodString>;
217
+ hardware_serial: z.ZodOptional<z.ZodString>;
209
218
  }, z.core.$strip>;
210
219
  export type CaptureDevice = z.infer<typeof captureDeviceSchema>;
211
220
  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({
@@ -143,6 +155,8 @@ export const captureDeviceSchema = z.object({
143
155
  product_name: z.string().optional(), // additive (Todo 20): real product name, deduped with a #N suffix when shared; absent ⇒ use display_name
144
156
  transport: deviceTransportSchema.optional(), // additive (Todo 20): how the device is attached; absent on legacy producers
145
157
  stable_id: z.string().optional(), // additive (Todo 20): reboot-stable hardware identity, distinct from input_id/device_path
158
+ 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
159
+ 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
160
  });
147
161
  // ---- transport telemetry (srt-stats event payload) ----
148
162
  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.4",
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",