@decartai/sdk 0.0.67 → 0.1.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.
Files changed (45) hide show
  1. package/README.md +55 -5
  2. package/dist/index.d.ts +7 -5
  3. package/dist/index.js +43 -28
  4. package/dist/process/client.js +1 -3
  5. package/dist/process/request.js +1 -3
  6. package/dist/queue/client.js +1 -3
  7. package/dist/queue/polling.js +1 -2
  8. package/dist/queue/request.js +1 -3
  9. package/dist/realtime/client.d.ts +23 -13
  10. package/dist/realtime/client.js +74 -244
  11. package/dist/realtime/config-realtime.js +49 -0
  12. package/dist/realtime/event-buffer.js +1 -3
  13. package/dist/realtime/initial-state-gate.js +21 -0
  14. package/dist/realtime/media-channel.js +82 -0
  15. package/dist/realtime/methods.js +12 -42
  16. package/dist/realtime/mirror-stream.js +1 -2
  17. package/dist/realtime/observability/diagnostics.d.ts +39 -0
  18. package/dist/realtime/observability/livekit-stats-provider.js +25 -0
  19. package/dist/realtime/observability/realtime-observability.js +173 -0
  20. package/dist/realtime/{telemetry-reporter.js → observability/telemetry-reporter.js} +12 -31
  21. package/dist/realtime/observability/webrtc-stats.d.ts +148 -0
  22. package/dist/realtime/observability/webrtc-stats.js +276 -0
  23. package/dist/realtime/signaling-channel.js +286 -0
  24. package/dist/realtime/stream-session.js +252 -0
  25. package/dist/realtime/subscribe-client.d.ts +2 -1
  26. package/dist/realtime/subscribe-client.js +115 -11
  27. package/dist/realtime/types.d.ts +25 -1
  28. package/dist/shared/model.d.ts +11 -1
  29. package/dist/shared/model.js +51 -14
  30. package/dist/shared/request.js +1 -3
  31. package/dist/shared/types.js +1 -3
  32. package/dist/tokens/client.js +1 -3
  33. package/dist/utils/env.js +1 -2
  34. package/dist/utils/errors.js +1 -2
  35. package/dist/utils/logger.js +1 -2
  36. package/dist/utils/media.js +43 -0
  37. package/dist/utils/platform.js +13 -0
  38. package/dist/utils/user-agent.js +1 -3
  39. package/dist/version.js +1 -2
  40. package/package.json +2 -1
  41. package/dist/realtime/diagnostics.d.ts +0 -78
  42. package/dist/realtime/webrtc-connection.js +0 -501
  43. package/dist/realtime/webrtc-manager.js +0 -189
  44. package/dist/realtime/webrtc-stats.d.ts +0 -59
  45. package/dist/realtime/webrtc-stats.js +0 -154
@@ -0,0 +1,276 @@
1
+ import { REALTIME_CONFIG } from "../config-realtime.js";
2
+ //#region src/realtime/observability/webrtc-stats.ts
3
+ var WebRTCStatsCollector = class {
4
+ source = null;
5
+ intervalId = null;
6
+ prevBytesVideo = 0;
7
+ prevBytesAudio = 0;
8
+ prevBytesSentVideo = 0;
9
+ prevTimestamp = 0;
10
+ prevPacketsLostVideo = 0;
11
+ prevFramesDropped = 0;
12
+ prevFreezeCount = 0;
13
+ prevFreezeDuration = 0;
14
+ prevPacketsLostAudio = 0;
15
+ prevNackCountInbound = 0;
16
+ onStats = null;
17
+ intervalMs;
18
+ constructor(options = {}) {
19
+ this.intervalMs = Math.max(options.intervalMs ?? REALTIME_CONFIG.observability.statsDefaultIntervalMs, REALTIME_CONFIG.observability.statsMinIntervalMs);
20
+ }
21
+ /** Attach to a stats provider and start polling. */
22
+ start(source, onStats) {
23
+ this.stop();
24
+ this.source = source;
25
+ this.onStats = onStats;
26
+ this.prevBytesVideo = 0;
27
+ this.prevBytesAudio = 0;
28
+ this.prevBytesSentVideo = 0;
29
+ this.prevTimestamp = 0;
30
+ this.prevPacketsLostVideo = 0;
31
+ this.prevFramesDropped = 0;
32
+ this.prevFreezeCount = 0;
33
+ this.prevFreezeDuration = 0;
34
+ this.prevPacketsLostAudio = 0;
35
+ this.prevNackCountInbound = 0;
36
+ this.intervalId = setInterval(() => this.collect(), this.intervalMs);
37
+ }
38
+ /** Stop polling and release resources. */
39
+ stop() {
40
+ if (this.intervalId !== null) {
41
+ clearInterval(this.intervalId);
42
+ this.intervalId = null;
43
+ }
44
+ this.source = null;
45
+ this.onStats = null;
46
+ }
47
+ isRunning() {
48
+ return this.intervalId !== null;
49
+ }
50
+ async collect() {
51
+ if (!this.source || !this.onStats) return;
52
+ try {
53
+ const rawStats = await this.source.getStats();
54
+ const stats = this.parse(rawStats);
55
+ this.onStats(stats);
56
+ } catch {
57
+ this.stop();
58
+ }
59
+ }
60
+ parse(rawStats) {
61
+ const now = performance.now();
62
+ const elapsed = this.prevTimestamp > 0 ? (now - this.prevTimestamp) / 1e3 : 0;
63
+ let video = null;
64
+ let audio = null;
65
+ let outboundVideo = null;
66
+ let remoteInbound = null;
67
+ const connection = {
68
+ currentRoundTripTime: null,
69
+ availableOutgoingBitrate: null,
70
+ selectedCandidatePairs: []
71
+ };
72
+ const succeededPairs = [];
73
+ rawStats.forEach((report) => {
74
+ if (report.type === "inbound-rtp" && report.kind === "video") {
75
+ const bytesReceived = report.bytesReceived ?? 0;
76
+ const bitrate = elapsed > 0 ? (bytesReceived - this.prevBytesVideo) * 8 / elapsed : 0;
77
+ this.prevBytesVideo = bytesReceived;
78
+ const r = report;
79
+ const packetsLost = r.packetsLost ?? 0;
80
+ const framesDropped = r.framesDropped ?? 0;
81
+ const freezeCount = r.freezeCount ?? 0;
82
+ const freezeDuration = r.totalFreezesDuration ?? 0;
83
+ const framesDecoded = r.framesDecoded ?? 0;
84
+ const nackCount = r.nackCount ?? 0;
85
+ const jbEmitted = r.jitterBufferEmittedCount ?? 0;
86
+ const totalDecodeTime = r.totalDecodeTime ?? 0;
87
+ const totalProcessingDelay = r.totalProcessingDelay ?? 0;
88
+ const totalInterFrameDelay = r.totalInterFrameDelay ?? 0;
89
+ const totalSquaredInterFrameDelay = r.totalSquaredInterFrameDelay ?? 0;
90
+ const jitterBufferDelay = r.jitterBufferDelay ?? 0;
91
+ const jitterBufferTargetDelay = r.jitterBufferTargetDelay ?? 0;
92
+ const jitterBufferMinimumDelay = r.jitterBufferMinimumDelay ?? 0;
93
+ const avgDecodeTimeMs = framesDecoded > 0 ? totalDecodeTime / framesDecoded * 1e3 : null;
94
+ const avgProcessingDelayMs = framesDecoded > 0 ? totalProcessingDelay / framesDecoded * 1e3 : null;
95
+ const avgInterFrameDelayMs = framesDecoded > 0 ? totalInterFrameDelay / framesDecoded * 1e3 : null;
96
+ const interFrameDelayStdDevMs = framesDecoded > 0 ? Math.sqrt(Math.max(0, totalSquaredInterFrameDelay / framesDecoded - (totalInterFrameDelay / framesDecoded) ** 2)) * 1e3 : null;
97
+ const avgJitterBufferMs = jbEmitted > 0 ? jitterBufferDelay / jbEmitted * 1e3 : null;
98
+ const jitterBufferTargetDelayMs = jbEmitted > 0 ? jitterBufferTargetDelay / jbEmitted * 1e3 : null;
99
+ const jitterBufferMinimumDelayMs = jbEmitted > 0 ? jitterBufferMinimumDelay / jbEmitted * 1e3 : null;
100
+ video = {
101
+ framesDecoded,
102
+ framesDropped,
103
+ framesReceived: r.framesReceived ?? 0,
104
+ keyFramesDecoded: r.keyFramesDecoded ?? 0,
105
+ framesPerSecond: r.framesPerSecond ?? 0,
106
+ frameWidth: r.frameWidth ?? 0,
107
+ frameHeight: r.frameHeight ?? 0,
108
+ bytesReceived,
109
+ packetsReceived: r.packetsReceived ?? 0,
110
+ packetsLost,
111
+ jitter: r.jitter ?? 0,
112
+ bitrate: Math.round(bitrate),
113
+ freezeCount,
114
+ totalFreezesDuration: freezeDuration,
115
+ packetsLostDelta: Math.max(0, packetsLost - this.prevPacketsLostVideo),
116
+ framesDroppedDelta: Math.max(0, framesDropped - this.prevFramesDropped),
117
+ freezeCountDelta: Math.max(0, freezeCount - this.prevFreezeCount),
118
+ freezeDurationDelta: Math.max(0, freezeDuration - this.prevFreezeDuration),
119
+ nackCount,
120
+ nackCountDelta: Math.max(0, nackCount - this.prevNackCountInbound),
121
+ pliCount: r.pliCount ?? 0,
122
+ firCount: r.firCount ?? 0,
123
+ avgDecodeTimeMs,
124
+ avgJitterBufferMs,
125
+ avgProcessingDelayMs,
126
+ avgInterFrameDelayMs,
127
+ interFrameDelayStdDevMs,
128
+ jitterBufferTargetDelayMs,
129
+ jitterBufferMinimumDelayMs,
130
+ decoderImplementation: r.decoderImplementation ?? ""
131
+ };
132
+ this.prevPacketsLostVideo = packetsLost;
133
+ this.prevFramesDropped = framesDropped;
134
+ this.prevFreezeCount = freezeCount;
135
+ this.prevFreezeDuration = freezeDuration;
136
+ this.prevNackCountInbound = nackCount;
137
+ }
138
+ if (report.type === "outbound-rtp" && report.kind === "video") {
139
+ const r = report;
140
+ const bytesSent = r.bytesSent ?? 0;
141
+ const packetsSent = r.packetsSent ?? 0;
142
+ const frameWidth = r.frameWidth ?? 0;
143
+ const frameHeight = r.frameHeight ?? 0;
144
+ const pixels = frameWidth * frameHeight;
145
+ const framesEncoded = r.framesEncoded ?? 0;
146
+ const totalEncodeTime = r.totalEncodeTime ?? 0;
147
+ const totalPacketSendDelay = r.totalPacketSendDelay ?? 0;
148
+ const qpSum = r.qpSum ?? 0;
149
+ const nackCount = r.nackCount ?? 0;
150
+ const pliCount = r.pliCount ?? 0;
151
+ const firCount = r.firCount ?? 0;
152
+ const retransmittedBytesSent = r.retransmittedBytesSent ?? 0;
153
+ const retransmittedPacketsSent = r.retransmittedPacketsSent ?? 0;
154
+ const targetBitrate = r.targetBitrate ?? null;
155
+ const avgEncodeTimeMs = framesEncoded > 0 ? totalEncodeTime / framesEncoded * 1e3 : null;
156
+ const avgPacketSendDelayMs = packetsSent > 0 ? totalPacketSendDelay / packetsSent * 1e3 : null;
157
+ const avgQp = framesEncoded > 0 ? qpSum / framesEncoded : null;
158
+ if (outboundVideo === null) outboundVideo = {
159
+ qualityLimitationReason: r.qualityLimitationReason ?? "none",
160
+ qualityLimitationDurations: r.qualityLimitationDurations ?? {},
161
+ bytesSent,
162
+ packetsSent,
163
+ framesPerSecond: r.framesPerSecond ?? 0,
164
+ frameWidth,
165
+ frameHeight,
166
+ bitrate: 0,
167
+ targetBitrateKbps: targetBitrate != null ? Math.round(targetBitrate / 1e3) : null,
168
+ avgEncodeTimeMs,
169
+ avgPacketSendDelayMs,
170
+ avgQp,
171
+ nackCount,
172
+ pliCount,
173
+ firCount,
174
+ retransmittedBytesSent,
175
+ retransmittedPacketsSent,
176
+ encoderImplementation: r.encoderImplementation ?? ""
177
+ };
178
+ else {
179
+ outboundVideo.bytesSent += bytesSent;
180
+ outboundVideo.packetsSent += packetsSent;
181
+ outboundVideo.nackCount += nackCount;
182
+ outboundVideo.pliCount += pliCount;
183
+ outboundVideo.firCount += firCount;
184
+ outboundVideo.retransmittedBytesSent += retransmittedBytesSent;
185
+ outboundVideo.retransmittedPacketsSent += retransmittedPacketsSent;
186
+ if (pixels > outboundVideo.frameWidth * outboundVideo.frameHeight) {
187
+ outboundVideo.frameWidth = frameWidth;
188
+ outboundVideo.frameHeight = frameHeight;
189
+ outboundVideo.framesPerSecond = r.framesPerSecond ?? 0;
190
+ outboundVideo.qualityLimitationReason = r.qualityLimitationReason ?? "none";
191
+ outboundVideo.qualityLimitationDurations = r.qualityLimitationDurations ?? {};
192
+ outboundVideo.targetBitrateKbps = targetBitrate != null ? Math.round(targetBitrate / 1e3) : null;
193
+ outboundVideo.avgEncodeTimeMs = avgEncodeTimeMs;
194
+ outboundVideo.avgPacketSendDelayMs = avgPacketSendDelayMs;
195
+ outboundVideo.avgQp = avgQp;
196
+ outboundVideo.encoderImplementation = r.encoderImplementation ?? "";
197
+ }
198
+ }
199
+ }
200
+ if (report.type === "remote-inbound-rtp" && report.kind === "video") {
201
+ const r = report;
202
+ remoteInbound = {
203
+ fractionLost: r.fractionLost ?? null,
204
+ jitter: r.jitter ?? null,
205
+ roundTripTime: r.roundTripTime ?? null
206
+ };
207
+ }
208
+ if (report.type === "inbound-rtp" && report.kind === "audio") {
209
+ const bytesReceived = report.bytesReceived ?? 0;
210
+ const bitrate = elapsed > 0 ? (bytesReceived - this.prevBytesAudio) * 8 / elapsed : 0;
211
+ this.prevBytesAudio = bytesReceived;
212
+ const r = report;
213
+ const audioPacketsLost = r.packetsLost ?? 0;
214
+ audio = {
215
+ bytesReceived,
216
+ packetsReceived: r.packetsReceived ?? 0,
217
+ packetsLost: audioPacketsLost,
218
+ jitter: r.jitter ?? 0,
219
+ bitrate: Math.round(bitrate),
220
+ packetsLostDelta: Math.max(0, audioPacketsLost - this.prevPacketsLostAudio)
221
+ };
222
+ this.prevPacketsLostAudio = audioPacketsLost;
223
+ }
224
+ if (report.type === "candidate-pair") {
225
+ const r = report;
226
+ if (r.state === "succeeded") {
227
+ connection.currentRoundTripTime = r.currentRoundTripTime ?? null;
228
+ connection.availableOutgoingBitrate = r.availableOutgoingBitrate ?? null;
229
+ const localId = r.localCandidateId;
230
+ const remoteId = r.remoteCandidateId;
231
+ if (localId && remoteId) succeededPairs.push({
232
+ localId,
233
+ remoteId
234
+ });
235
+ }
236
+ }
237
+ });
238
+ if (succeededPairs.length > 0) {
239
+ const toInfo = (id) => {
240
+ const c = rawStats.get(id);
241
+ if (!c) return null;
242
+ return {
243
+ candidateType: c.candidateType ?? "",
244
+ address: c.address ?? c.ip ?? "",
245
+ port: c.port ?? 0,
246
+ protocol: c.protocol ?? ""
247
+ };
248
+ };
249
+ for (const { localId, remoteId } of succeededPairs) {
250
+ const local = toInfo(localId);
251
+ const remote = toInfo(remoteId);
252
+ if (local && remote) connection.selectedCandidatePairs.push({
253
+ local,
254
+ remote
255
+ });
256
+ }
257
+ }
258
+ const ov = outboundVideo;
259
+ if (ov !== null) {
260
+ const outBitrate = elapsed > 0 ? (ov.bytesSent - this.prevBytesSentVideo) * 8 / elapsed : 0;
261
+ ov.bitrate = Math.max(0, Math.round(outBitrate));
262
+ this.prevBytesSentVideo = ov.bytesSent;
263
+ }
264
+ this.prevTimestamp = now;
265
+ return {
266
+ timestamp: Date.now(),
267
+ video,
268
+ audio,
269
+ outboundVideo,
270
+ connection,
271
+ remoteInbound
272
+ };
273
+ }
274
+ };
275
+ //#endregion
276
+ export { WebRTCStatsCollector };
@@ -0,0 +1,286 @@
1
+ import { buildUserAgent } from "../utils/user-agent.js";
2
+ import { createConsoleLogger } from "../utils/logger.js";
3
+ import { REALTIME_CONFIG } from "./config-realtime.js";
4
+ import mitt from "mitt";
5
+ //#region src/realtime/signaling-channel.ts
6
+ var SignalingChannel = class {
7
+ ws = null;
8
+ events = mitt();
9
+ pendingAcks = [];
10
+ pendingRoomInfo = null;
11
+ connected = false;
12
+ closing = false;
13
+ logger;
14
+ constructor(config) {
15
+ this.config = config;
16
+ this.logger = config.logger ?? createConsoleLogger("warn");
17
+ }
18
+ on(event, handler) {
19
+ this.events.on(event, handler);
20
+ }
21
+ off(event, handler) {
22
+ this.events.off(event, handler);
23
+ }
24
+ async openAndJoin(opts = {}) {
25
+ const connectTimeout = opts.connectTimeout ?? REALTIME_CONFIG.signaling.connectTimeoutMs;
26
+ const handshakeTimeout = opts.handshakeTimeout ?? REALTIME_CONFIG.signaling.handshakeTimeoutMs;
27
+ this.config.observability?.startPhase("websocket-open");
28
+ await this.openSocket(connectTimeout);
29
+ this.config.observability?.endPhase("websocket-open", { success: true });
30
+ this.config.observability?.startPhase("room-join");
31
+ const roomInfoWait = this.waitForRoomInfo(handshakeTimeout);
32
+ if (!this.writeMessage({ type: "livekit_join" })) {
33
+ roomInfoWait.cancel();
34
+ throw new Error("WebSocket is not open");
35
+ }
36
+ let roomInfo;
37
+ try {
38
+ roomInfo = await roomInfoWait.promise;
39
+ } catch (error) {
40
+ this.rejectAllPending(error instanceof Error ? error : new Error(String(error)));
41
+ throw error;
42
+ }
43
+ this.config.observability?.endPhase("room-join", { success: true });
44
+ this.connected = true;
45
+ const initialStateAck = this.sendInitialStateTracked(opts.initialState);
46
+ initialStateAck.catch(() => {});
47
+ return {
48
+ roomInfo,
49
+ initialStateAck
50
+ };
51
+ }
52
+ async sendInitialStateTracked(initialState) {
53
+ if (!initialState) return;
54
+ this.config.observability?.startPhase("initial-state-handshake");
55
+ await this.sendInitialState(initialState);
56
+ this.config.observability?.endPhase("initial-state-handshake", { success: true });
57
+ }
58
+ close() {
59
+ this.closing = true;
60
+ this.connected = false;
61
+ const ws = this.ws;
62
+ this.ws = null;
63
+ if (ws) try {
64
+ ws.close();
65
+ } catch {}
66
+ this.rejectPendingRoomInfo(/* @__PURE__ */ new Error("Control channel closed"));
67
+ this.rejectAllPending(/* @__PURE__ */ new Error("Control channel closed"));
68
+ }
69
+ async sendPrompt(text, opts = {}) {
70
+ const ack = await this.request({
71
+ message: {
72
+ type: "prompt",
73
+ prompt: text,
74
+ enhance_prompt: opts.enhance ?? true
75
+ },
76
+ matchAck: (msg) => msg.type === "prompt_ack" && msg.prompt === text,
77
+ timeoutMs: opts.timeout ?? REALTIME_CONFIG.signaling.requestTimeoutMs,
78
+ label: "Prompt send"
79
+ });
80
+ if (!ack.success) throw new Error(ack.error ?? "Failed to send prompt");
81
+ }
82
+ async setImage(image, opts = {}) {
83
+ const message = {
84
+ type: "set_image",
85
+ image_data: image
86
+ };
87
+ if (opts.prompt !== void 0) message.prompt = opts.prompt;
88
+ if (opts.enhance !== void 0) message.enhance_prompt = opts.enhance;
89
+ const ack = await this.request({
90
+ message,
91
+ matchAck: (msg) => msg.type === "set_image_ack",
92
+ timeoutMs: opts.timeout ?? REALTIME_CONFIG.signaling.requestTimeoutMs,
93
+ label: "Image send"
94
+ });
95
+ if (!ack.success) throw new Error(ack.error ?? "Failed to send image");
96
+ }
97
+ async openSocket(timeout) {
98
+ const userAgent = encodeURIComponent(buildUserAgent(this.config.integration));
99
+ const separator = this.config.url.includes("?") ? "&" : "?";
100
+ const wsUrl = `${this.config.url}${separator}user_agent=${userAgent}`;
101
+ this.closing = false;
102
+ await new Promise((resolve, reject) => {
103
+ const timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`WebSocket open timeout (${timeout}ms)`)), timeout);
104
+ const ws = new WebSocket(wsUrl);
105
+ this.ws = ws;
106
+ ws.onopen = () => {
107
+ clearTimeout(timer);
108
+ resolve();
109
+ };
110
+ ws.onclose = (e) => {
111
+ clearTimeout(timer);
112
+ const wasConnected = this.connected;
113
+ const pendingCount = this.pendingAcks.length;
114
+ this.connected = false;
115
+ this.ws = null;
116
+ this.logger.warn("signaling: websocket closed", {
117
+ code: e.code,
118
+ reason: e.reason,
119
+ wasConnected,
120
+ closing: this.closing,
121
+ pendingAcks: pendingCount
122
+ });
123
+ const error = /* @__PURE__ */ new Error(`WebSocket closed: ${e.code} ${e.reason}`);
124
+ this.rejectPendingRoomInfo(error);
125
+ this.rejectAllPending(error);
126
+ if (wasConnected || this.closing) this.events.emit("closed", {
127
+ code: e.code,
128
+ reason: e.reason
129
+ });
130
+ else reject(error);
131
+ };
132
+ ws.onerror = () => {};
133
+ ws.onmessage = (e) => {
134
+ try {
135
+ this.handleMessage(JSON.parse(e.data));
136
+ } catch {}
137
+ };
138
+ });
139
+ }
140
+ waitForRoomInfo(timeoutMs) {
141
+ let cleanup = () => {};
142
+ return {
143
+ promise: new Promise((resolve, reject) => {
144
+ let timer = setTimeout(() => {
145
+ cleanup();
146
+ this.logger.warn("signaling: livekit_room_info timeout", { timeoutMs });
147
+ reject(/* @__PURE__ */ new Error(`livekit_room_info timeout (${timeoutMs}ms)`));
148
+ }, timeoutMs);
149
+ const pendingRoomInfo = {
150
+ resolve: (info) => {
151
+ cleanup();
152
+ resolve(info);
153
+ },
154
+ reject: (err) => {
155
+ cleanup();
156
+ reject(err);
157
+ },
158
+ cancel: () => {
159
+ cleanup();
160
+ },
161
+ pauseTimeout: () => {
162
+ if (timer) {
163
+ clearTimeout(timer);
164
+ timer = null;
165
+ }
166
+ }
167
+ };
168
+ cleanup = () => {
169
+ if (timer) {
170
+ clearTimeout(timer);
171
+ timer = null;
172
+ }
173
+ if (this.pendingRoomInfo === pendingRoomInfo) this.pendingRoomInfo = null;
174
+ };
175
+ this.pendingRoomInfo = pendingRoomInfo;
176
+ }),
177
+ cancel: cleanup
178
+ };
179
+ }
180
+ async sendInitialState(initialState) {
181
+ if (!initialState) return;
182
+ if (initialState.image !== void 0) {
183
+ await this.setImage(initialState.image, {
184
+ prompt: initialState.prompt,
185
+ enhance: initialState.enhance
186
+ });
187
+ return;
188
+ }
189
+ if (initialState.prompt !== void 0 && initialState.prompt !== null) await this.sendPrompt(initialState.prompt, { enhance: initialState.enhance });
190
+ }
191
+ async request({ message, matchAck, timeoutMs, label }) {
192
+ return new Promise((resolve, reject) => {
193
+ const timer = setTimeout(() => {
194
+ cleanup();
195
+ this.logger.warn("signaling: ack timed out", {
196
+ label,
197
+ timeoutMs
198
+ });
199
+ reject(/* @__PURE__ */ new Error(`${label} timed out`));
200
+ }, timeoutMs);
201
+ const entry = {
202
+ matches: matchAck,
203
+ onMatch: (msg) => {
204
+ cleanup();
205
+ resolve(msg);
206
+ },
207
+ reject: (err) => {
208
+ cleanup();
209
+ reject(err);
210
+ }
211
+ };
212
+ const cleanup = () => {
213
+ clearTimeout(timer);
214
+ this.pendingAcks = this.pendingAcks.filter((e) => e !== entry);
215
+ };
216
+ this.pendingAcks.push(entry);
217
+ if (!this.writeMessage(message)) {
218
+ cleanup();
219
+ reject(/* @__PURE__ */ new Error("WebSocket is not open"));
220
+ }
221
+ });
222
+ }
223
+ writeMessage(message) {
224
+ if (this.ws?.readyState !== WebSocket.OPEN) return false;
225
+ this.ws.send(JSON.stringify(message));
226
+ return true;
227
+ }
228
+ handleMessage(msg) {
229
+ for (const ack of [...this.pendingAcks]) if (ack.matches(msg)) {
230
+ ack.onMatch(msg);
231
+ break;
232
+ }
233
+ switch (msg.type) {
234
+ case "livekit_room_info":
235
+ this.resolvePendingRoomInfo({
236
+ livekitUrl: msg.livekit_url,
237
+ token: msg.token,
238
+ roomName: msg.room_name,
239
+ sessionId: msg.session_id
240
+ });
241
+ break;
242
+ case "queue_position":
243
+ this.pendingRoomInfo?.pauseTimeout();
244
+ this.events.emit("queuePosition", {
245
+ position: msg.position,
246
+ queueSize: msg.queue_size
247
+ });
248
+ break;
249
+ case "generation_tick":
250
+ this.events.emit("generationTick", { seconds: msg.seconds });
251
+ break;
252
+ case "generation_ended":
253
+ this.events.emit("generationEnded", {
254
+ seconds: msg.seconds,
255
+ reason: msg.reason
256
+ });
257
+ break;
258
+ case "error": {
259
+ const error = new Error(msg.error);
260
+ error.source = "server";
261
+ this.logger.error("signaling: server error received", { error: msg.error });
262
+ this.events.emit("serverError", error);
263
+ this.rejectPendingRoomInfo(error);
264
+ this.rejectAllPending(error);
265
+ break;
266
+ }
267
+ }
268
+ }
269
+ resolvePendingRoomInfo(info) {
270
+ const pending = this.pendingRoomInfo;
271
+ if (!pending) return;
272
+ pending.resolve(info);
273
+ }
274
+ rejectPendingRoomInfo(error) {
275
+ const pending = this.pendingRoomInfo;
276
+ if (!pending) return;
277
+ pending.reject(error);
278
+ }
279
+ rejectAllPending(error) {
280
+ const pending = this.pendingAcks;
281
+ this.pendingAcks = [];
282
+ for (const entry of pending) entry.reject(error);
283
+ }
284
+ };
285
+ //#endregion
286
+ export { SignalingChannel };