@decartai/sdk 0.0.47 → 0.0.49

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.
@@ -4,15 +4,28 @@ import mitt from "mitt";
4
4
  //#region src/realtime/webrtc-connection.ts
5
5
  const ICE_SERVERS = [{ urls: "stun:stun.l.google.com:19302" }];
6
6
  const AVATAR_SETUP_TIMEOUT_MS = 3e4;
7
+ const noopDiagnostic = () => {};
7
8
  var WebRTCConnection = class {
8
9
  pc = null;
9
10
  ws = null;
10
11
  localStream = null;
11
12
  connectionReject = null;
13
+ logger;
14
+ emitDiagnostic;
12
15
  state = "disconnected";
13
16
  websocketMessagesEmitter = mitt();
14
17
  constructor(callbacks = {}) {
15
18
  this.callbacks = callbacks;
19
+ this.logger = callbacks.logger ?? {
20
+ debug() {},
21
+ info() {},
22
+ warn() {},
23
+ error() {}
24
+ };
25
+ this.emitDiagnostic = callbacks.onDiagnostic ?? noopDiagnostic;
26
+ }
27
+ getPeerConnection() {
28
+ return this.pc;
16
29
  }
17
30
  async connect(url, localStream, timeout, integration) {
18
31
  const deadline = Date.now() + timeout;
@@ -25,24 +38,37 @@ var WebRTCConnection = class {
25
38
  });
26
39
  connectAbort.catch(() => {});
27
40
  this.connectionReject = (error) => rejectConnect(error);
41
+ const totalStart = performance.now();
28
42
  try {
43
+ const wsStart = performance.now();
29
44
  await Promise.race([new Promise((resolve, reject) => {
30
45
  const timer = setTimeout(() => reject(/* @__PURE__ */ new Error("WebSocket timeout")), timeout);
31
46
  this.ws = new WebSocket(wsUrl);
32
47
  this.ws.onopen = () => {
33
48
  clearTimeout(timer);
49
+ this.emitDiagnostic("phaseTiming", {
50
+ phase: "websocket",
51
+ durationMs: performance.now() - wsStart,
52
+ success: true
53
+ });
34
54
  resolve();
35
55
  };
36
56
  this.ws.onmessage = (e) => {
37
57
  try {
38
58
  this.handleSignalingMessage(JSON.parse(e.data));
39
59
  } catch (err) {
40
- console.error("[WebRTC] Parse error:", err);
60
+ this.logger.error("Signaling message parse error", { error: String(err) });
41
61
  }
42
62
  };
43
63
  this.ws.onerror = () => {
44
64
  clearTimeout(timer);
45
65
  const error = /* @__PURE__ */ new Error("WebSocket error");
66
+ this.emitDiagnostic("phaseTiming", {
67
+ phase: "websocket",
68
+ durationMs: performance.now() - wsStart,
69
+ success: false,
70
+ error: error.message
71
+ });
46
72
  reject(error);
47
73
  rejectConnect(error);
48
74
  };
@@ -53,24 +79,65 @@ var WebRTCConnection = class {
53
79
  rejectConnect(/* @__PURE__ */ new Error("WebSocket closed"));
54
80
  };
55
81
  }), connectAbort]);
56
- if (this.callbacks.avatarImageBase64) await Promise.race([this.sendAvatarImage(this.callbacks.avatarImageBase64), connectAbort]);
57
- if (this.callbacks.initialPrompt) await Promise.race([this.sendInitialPrompt(this.callbacks.initialPrompt), connectAbort]);
82
+ if (this.callbacks.initialImage) {
83
+ const imageStart = performance.now();
84
+ await Promise.race([this.setImageBase64(this.callbacks.initialImage, {
85
+ prompt: this.callbacks.initialPrompt?.text,
86
+ enhance: this.callbacks.initialPrompt?.enhance
87
+ }), connectAbort]);
88
+ this.emitDiagnostic("phaseTiming", {
89
+ phase: "avatar-image",
90
+ durationMs: performance.now() - imageStart,
91
+ success: true
92
+ });
93
+ } else if (this.callbacks.initialPrompt) {
94
+ const promptStart = performance.now();
95
+ await Promise.race([this.sendInitialPrompt(this.callbacks.initialPrompt), connectAbort]);
96
+ this.emitDiagnostic("phaseTiming", {
97
+ phase: "initial-prompt",
98
+ durationMs: performance.now() - promptStart,
99
+ success: true
100
+ });
101
+ }
102
+ const handshakeStart = performance.now();
58
103
  await this.setupNewPeerConnection();
59
104
  await Promise.race([new Promise((resolve, reject) => {
60
105
  const checkConnection = setInterval(() => {
61
106
  if (this.state === "connected" || this.state === "generating") {
62
107
  clearInterval(checkConnection);
108
+ this.emitDiagnostic("phaseTiming", {
109
+ phase: "webrtc-handshake",
110
+ durationMs: performance.now() - handshakeStart,
111
+ success: true
112
+ });
63
113
  resolve();
64
114
  } else if (this.state === "disconnected") {
65
115
  clearInterval(checkConnection);
116
+ this.emitDiagnostic("phaseTiming", {
117
+ phase: "webrtc-handshake",
118
+ durationMs: performance.now() - handshakeStart,
119
+ success: false,
120
+ error: "Connection lost during handshake"
121
+ });
66
122
  reject(/* @__PURE__ */ new Error("Connection lost during WebRTC handshake"));
67
123
  } else if (Date.now() >= deadline) {
68
124
  clearInterval(checkConnection);
125
+ this.emitDiagnostic("phaseTiming", {
126
+ phase: "webrtc-handshake",
127
+ durationMs: performance.now() - handshakeStart,
128
+ success: false,
129
+ error: "Timeout"
130
+ });
69
131
  reject(/* @__PURE__ */ new Error("Connection timeout"));
70
132
  }
71
133
  }, 100);
72
134
  connectAbort.catch(() => clearInterval(checkConnection));
73
135
  }), connectAbort]);
136
+ this.emitDiagnostic("phaseTiming", {
137
+ phase: "total",
138
+ durationMs: performance.now() - totalStart,
139
+ success: true
140
+ });
74
141
  } finally {
75
142
  this.connectionReject = null;
76
143
  }
@@ -79,6 +146,7 @@ var WebRTCConnection = class {
79
146
  try {
80
147
  if (msg.type === "error") {
81
148
  const error = new Error(msg.error);
149
+ error.source = "server";
82
150
  this.callbacks.onError?.(error);
83
151
  if (this.connectionReject) {
84
152
  this.connectionReject(error);
@@ -141,7 +209,14 @@ var WebRTCConnection = class {
141
209
  });
142
210
  break;
143
211
  case "ice-candidate":
144
- if (msg.candidate) await this.pc.addIceCandidate(msg.candidate);
212
+ if (msg.candidate) {
213
+ await this.pc.addIceCandidate(msg.candidate);
214
+ this.emitDiagnostic("iceCandidate", {
215
+ source: "remote",
216
+ candidateType: msg.candidate.candidate?.match(/typ (\w+)/)?.[1] ?? "unknown",
217
+ protocol: msg.candidate.candidate?.match(/udp|tcp/i)?.[0]?.toLowerCase() ?? "unknown"
218
+ });
219
+ }
145
220
  break;
146
221
  case "ice-restart": {
147
222
  const turnConfig = msg.turn_config;
@@ -150,7 +225,7 @@ var WebRTCConnection = class {
150
225
  }
151
226
  }
152
227
  } catch (error) {
153
- console.error("[WebRTC] Error:", error);
228
+ this.logger.error("Signaling handler error", { error: String(error) });
154
229
  this.callbacks.onError?.(error);
155
230
  this.connectionReject?.(error);
156
231
  }
@@ -160,18 +235,9 @@ var WebRTCConnection = class {
160
235
  this.ws.send(JSON.stringify(message));
161
236
  return true;
162
237
  }
163
- console.warn("[WebRTC] Message dropped: WebSocket is not open");
238
+ this.logger.warn("Message dropped: WebSocket is not open");
164
239
  return false;
165
240
  }
166
- async sendAvatarImage(imageBase64) {
167
- return this.setImageBase64(imageBase64);
168
- }
169
- /**
170
- * Send an image to the server (e.g., as a reference for inference).
171
- * Can be called after connection is established.
172
- * Pass null to clear the reference image or use a placeholder.
173
- * Optionally include a prompt to send with the image.
174
- */
175
241
  async setImageBase64(imageBase64, options) {
176
242
  return new Promise((resolve, reject) => {
177
243
  const timeoutId = setTimeout(() => {
@@ -249,7 +315,7 @@ var WebRTCConnection = class {
249
315
  this.pc = new RTCPeerConnection({ iceServers });
250
316
  this.setState("connecting");
251
317
  if (this.localStream) {
252
- if (this.callbacks.isAvatarLive) this.pc.addTransceiver("video", { direction: "recvonly" });
318
+ if (this.callbacks.modelName === "live_avatar") this.pc.addTransceiver("video", { direction: "recvonly" });
253
319
  this.localStream.getTracks().forEach((track) => {
254
320
  if (this.pc && this.localStream) this.pc.addTrack(track, this.localStream);
255
321
  });
@@ -271,22 +337,90 @@ var WebRTCConnection = class {
271
337
  type: "ice-candidate",
272
338
  candidate: e.candidate
273
339
  });
340
+ if (e.candidate) this.emitDiagnostic("iceCandidate", {
341
+ source: "local",
342
+ candidateType: e.candidate.type ?? "unknown",
343
+ protocol: e.candidate.protocol ?? "unknown",
344
+ address: e.candidate.address ?? void 0,
345
+ port: e.candidate.port ?? void 0
346
+ });
274
347
  };
348
+ let prevPcState = "new";
275
349
  this.pc.onconnectionstatechange = () => {
276
350
  if (!this.pc) return;
277
351
  const s = this.pc.connectionState;
352
+ this.emitDiagnostic("peerConnectionStateChange", {
353
+ state: s,
354
+ previousState: prevPcState,
355
+ timestampMs: performance.now()
356
+ });
357
+ prevPcState = s;
358
+ if (s === "connected") this.emitSelectedCandidatePair();
278
359
  const nextState = s === "connected" ? "connected" : ["connecting", "new"].includes(s) ? "connecting" : "disconnected";
279
360
  if (this.state === "generating" && nextState !== "disconnected") return;
280
361
  this.setState(nextState);
281
362
  };
363
+ let prevIceState = "new";
282
364
  this.pc.oniceconnectionstatechange = () => {
283
- if (this.pc?.iceConnectionState === "failed") {
365
+ if (!this.pc) return;
366
+ const newIceState = this.pc.iceConnectionState;
367
+ this.emitDiagnostic("iceStateChange", {
368
+ state: newIceState,
369
+ previousState: prevIceState,
370
+ timestampMs: performance.now()
371
+ });
372
+ prevIceState = newIceState;
373
+ if (newIceState === "failed") {
284
374
  this.setState("disconnected");
285
375
  this.callbacks.onError?.(/* @__PURE__ */ new Error("ICE connection failed"));
286
376
  }
287
377
  };
378
+ let prevSignalingState = "stable";
379
+ this.pc.onsignalingstatechange = () => {
380
+ if (!this.pc) return;
381
+ const newState = this.pc.signalingState;
382
+ this.emitDiagnostic("signalingStateChange", {
383
+ state: newState,
384
+ previousState: prevSignalingState,
385
+ timestampMs: performance.now()
386
+ });
387
+ prevSignalingState = newState;
388
+ };
288
389
  this.handleSignalingMessage({ type: "ready" });
289
390
  }
391
+ async emitSelectedCandidatePair() {
392
+ if (!this.pc) return;
393
+ try {
394
+ const stats = await this.pc.getStats();
395
+ let found = false;
396
+ stats.forEach((report) => {
397
+ if (found) return;
398
+ if (report.type === "candidate-pair" && report.state === "succeeded") {
399
+ found = true;
400
+ let localCandidate;
401
+ let remoteCandidate;
402
+ stats.forEach((r) => {
403
+ if (r.id === report.localCandidateId) localCandidate = r;
404
+ if (r.id === report.remoteCandidateId) remoteCandidate = r;
405
+ });
406
+ if (localCandidate && remoteCandidate) this.emitDiagnostic("selectedCandidatePair", {
407
+ local: {
408
+ candidateType: String(localCandidate.candidateType ?? "unknown"),
409
+ protocol: String(localCandidate.protocol ?? "unknown"),
410
+ address: localCandidate.address,
411
+ port: localCandidate.port
412
+ },
413
+ remote: {
414
+ candidateType: String(remoteCandidate.candidateType ?? "unknown"),
415
+ protocol: String(remoteCandidate.protocol ?? "unknown"),
416
+ address: remoteCandidate.address,
417
+ port: remoteCandidate.port
418
+ }
419
+ });
420
+ }
421
+ });
422
+ } catch {}
423
+ }
290
424
  cleanup() {
291
425
  this.pc?.close();
292
426
  this.pc = null;
@@ -298,17 +432,17 @@ var WebRTCConnection = class {
298
432
  applyCodecPreference(preferredCodecName) {
299
433
  if (!this.pc) return;
300
434
  if (typeof RTCRtpSender === "undefined" || typeof RTCRtpSender.getCapabilities !== "function") {
301
- console.warn("RTCRtpSender capabilities are not available in this environment.");
435
+ this.logger.debug("RTCRtpSender capabilities not available in this environment");
302
436
  return;
303
437
  }
304
438
  const videoTransceiver = this.pc.getTransceivers().find((r) => r.sender.track?.kind === "video" || r.receiver.track?.kind === "video");
305
439
  if (!videoTransceiver) {
306
- console.error("Could not find video transceiver. Ensure track is added to peer connection.");
440
+ this.logger.warn("Video transceiver not found for codec preference");
307
441
  return;
308
442
  }
309
443
  const capabilities = RTCRtpSender.getCapabilities("video");
310
444
  if (!capabilities) {
311
- console.error("Could not get video sender capabilities.");
445
+ this.logger.warn("Video sender capabilities unavailable");
312
446
  return;
313
447
  }
314
448
  const preferredCodecs = [];
@@ -319,13 +453,13 @@ var WebRTCConnection = class {
319
453
  });
320
454
  const orderedCodecs = [...preferredCodecs, ...otherCodecs];
321
455
  if (orderedCodecs.length === 0) {
322
- console.warn("No video codecs found to set preferences for.");
456
+ this.logger.debug("No video codecs found for preference setting");
323
457
  return;
324
458
  }
325
459
  try {
326
460
  videoTransceiver.setCodecPreferences(orderedCodecs);
327
461
  } catch {
328
- console.warn("[WebRTC] setCodecPreferences not supported, skipping codec preference.");
462
+ this.logger.debug("setCodecPreferences not supported, skipping");
329
463
  }
330
464
  }
331
465
  modifyVP8Bitrate(offer) {
@@ -20,6 +20,7 @@ const RETRY_OPTIONS = {
20
20
  var WebRTCManager = class {
21
21
  connection;
22
22
  config;
23
+ logger;
23
24
  localStream = null;
24
25
  subscribeMode = false;
25
26
  managerState = "disconnected";
@@ -29,6 +30,12 @@ var WebRTCManager = class {
29
30
  reconnectGeneration = 0;
30
31
  constructor(config) {
31
32
  this.config = config;
33
+ this.logger = config.logger ?? {
34
+ debug() {},
35
+ info() {},
36
+ warn() {},
37
+ error() {}
38
+ };
32
39
  this.connection = new WebRTCConnection({
33
40
  onRemoteStream: config.onRemoteStream,
34
41
  onStateChange: (state) => this.handleConnectionStateChange(state),
@@ -36,9 +43,11 @@ var WebRTCManager = class {
36
43
  customizeOffer: config.customizeOffer,
37
44
  vp8MinBitrate: config.vp8MinBitrate,
38
45
  vp8StartBitrate: config.vp8StartBitrate,
39
- isAvatarLive: config.isAvatarLive,
40
- avatarImageBase64: config.avatarImageBase64,
41
- initialPrompt: config.initialPrompt
46
+ modelName: config.modelName,
47
+ initialImage: config.initialImage,
48
+ initialPrompt: config.initialPrompt,
49
+ logger: this.logger,
50
+ onDiagnostic: config.onDiagnostic
42
51
  });
43
52
  }
44
53
  emitState(state) {
@@ -72,8 +81,11 @@ var WebRTCManager = class {
72
81
  const reconnectGeneration = ++this.reconnectGeneration;
73
82
  this.isReconnecting = true;
74
83
  this.emitState("reconnecting");
84
+ const reconnectStart = performance.now();
75
85
  try {
86
+ let attemptCount = 0;
76
87
  await pRetry(async () => {
88
+ attemptCount++;
77
89
  if (this.intentionalDisconnect || reconnectGeneration !== this.reconnectGeneration) throw new AbortError("Reconnect cancelled");
78
90
  if (!this.subscribeMode && !this.localStream) throw new AbortError("Reconnect cancelled: no local stream");
79
91
  this.connection.cleanup();
@@ -86,7 +98,17 @@ var WebRTCManager = class {
86
98
  ...RETRY_OPTIONS,
87
99
  onFailedAttempt: (error) => {
88
100
  if (this.intentionalDisconnect || reconnectGeneration !== this.reconnectGeneration) return;
89
- console.error(`[WebRTC] Reconnect attempt failed: ${error.message}`);
101
+ this.logger.warn("Reconnect attempt failed", {
102
+ error: error.message,
103
+ attempt: error.attemptNumber
104
+ });
105
+ this.config.onDiagnostic?.("reconnect", {
106
+ attempt: error.attemptNumber,
107
+ maxAttempts: RETRY_OPTIONS.retries + 1,
108
+ durationMs: performance.now() - reconnectStart,
109
+ success: false,
110
+ error: error.message
111
+ });
90
112
  this.connection.cleanup();
91
113
  },
92
114
  shouldRetry: (error) => {
@@ -95,6 +117,12 @@ var WebRTCManager = class {
95
117
  return !PERMANENT_ERRORS.some((err) => msg.includes(err));
96
118
  }
97
119
  });
120
+ this.config.onDiagnostic?.("reconnect", {
121
+ attempt: attemptCount,
122
+ maxAttempts: RETRY_OPTIONS.retries + 1,
123
+ durationMs: performance.now() - reconnectStart,
124
+ success: true
125
+ });
98
126
  } catch (error) {
99
127
  this.isReconnecting = false;
100
128
  if (this.intentionalDisconnect || reconnectGeneration !== this.reconnectGeneration) return;
@@ -117,7 +145,10 @@ var WebRTCManager = class {
117
145
  }, {
118
146
  ...RETRY_OPTIONS,
119
147
  onFailedAttempt: (error) => {
120
- console.error(`[WebRTC] Failed to connect: ${error.message}`);
148
+ this.logger.warn("Connection attempt failed", {
149
+ error: error.message,
150
+ attempt: error.attemptNumber
151
+ });
121
152
  this.connection.cleanup();
122
153
  },
123
154
  shouldRetry: (error) => {
@@ -144,6 +175,9 @@ var WebRTCManager = class {
144
175
  getConnectionState() {
145
176
  return this.managerState;
146
177
  }
178
+ getPeerConnection() {
179
+ return this.connection.getPeerConnection();
180
+ }
147
181
  getWebsocketMessageEmitter() {
148
182
  return this.connection.websocketMessagesEmitter;
149
183
  }
@@ -0,0 +1,59 @@
1
+ //#region src/realtime/webrtc-stats.d.ts
2
+ type WebRTCStats = {
3
+ timestamp: number;
4
+ video: {
5
+ framesDecoded: number;
6
+ framesDropped: number;
7
+ framesPerSecond: number;
8
+ frameWidth: number;
9
+ frameHeight: number;
10
+ bytesReceived: number;
11
+ packetsReceived: number;
12
+ packetsLost: number;
13
+ jitter: number;
14
+ /** Estimated inbound bitrate in bits/sec, computed from bytesReceived delta. */
15
+ bitrate: number;
16
+ freezeCount: number;
17
+ totalFreezesDuration: number;
18
+ /** Delta: packets lost since previous sample. */
19
+ packetsLostDelta: number;
20
+ /** Delta: frames dropped since previous sample. */
21
+ framesDroppedDelta: number;
22
+ /** Delta: freeze count since previous sample. */
23
+ freezeCountDelta: number;
24
+ /** Delta: freeze duration (seconds) since previous sample. */
25
+ freezeDurationDelta: number;
26
+ } | null;
27
+ audio: {
28
+ bytesReceived: number;
29
+ packetsReceived: number;
30
+ packetsLost: number;
31
+ jitter: number;
32
+ /** Estimated inbound bitrate in bits/sec, computed from bytesReceived delta. */
33
+ bitrate: number;
34
+ /** Delta: packets lost since previous sample. */
35
+ packetsLostDelta: number;
36
+ } | null;
37
+ /** Outbound video track stats (from the local camera/screen share being sent). */
38
+ outboundVideo: {
39
+ /** Why the encoder is limiting quality: "none", "bandwidth", "cpu", or "other". */
40
+ qualityLimitationReason: string;
41
+ /** Cumulative time (seconds) spent in each quality limitation state. */
42
+ qualityLimitationDurations: Record<string, number>;
43
+ bytesSent: number;
44
+ packetsSent: number;
45
+ framesPerSecond: number;
46
+ frameWidth: number;
47
+ frameHeight: number;
48
+ /** Estimated outbound bitrate in bits/sec, computed from bytesSent delta. */
49
+ bitrate: number;
50
+ } | null;
51
+ connection: {
52
+ /** Current round-trip time in seconds, or null if unavailable. */
53
+ currentRoundTripTime: number | null;
54
+ /** Available outgoing bitrate estimate in bits/sec, or null if unavailable. */
55
+ availableOutgoingBitrate: number | null;
56
+ };
57
+ };
58
+ //#endregion
59
+ export { WebRTCStats };
@@ -0,0 +1,154 @@
1
+ //#region src/realtime/webrtc-stats.ts
2
+ const DEFAULT_INTERVAL_MS = 1e3;
3
+ const MIN_INTERVAL_MS = 500;
4
+ var WebRTCStatsCollector = class {
5
+ pc = null;
6
+ intervalId = null;
7
+ prevBytesVideo = 0;
8
+ prevBytesAudio = 0;
9
+ prevBytesSentVideo = 0;
10
+ prevTimestamp = 0;
11
+ prevPacketsLostVideo = 0;
12
+ prevFramesDropped = 0;
13
+ prevFreezeCount = 0;
14
+ prevFreezeDuration = 0;
15
+ prevPacketsLostAudio = 0;
16
+ onStats = null;
17
+ intervalMs;
18
+ constructor(options = {}) {
19
+ this.intervalMs = Math.max(options.intervalMs ?? DEFAULT_INTERVAL_MS, MIN_INTERVAL_MS);
20
+ }
21
+ /** Attach to a peer connection and start polling. */
22
+ start(pc, onStats) {
23
+ this.stop();
24
+ this.pc = pc;
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.intervalId = setInterval(() => this.collect(), this.intervalMs);
36
+ }
37
+ /** Stop polling and release resources. */
38
+ stop() {
39
+ if (this.intervalId !== null) {
40
+ clearInterval(this.intervalId);
41
+ this.intervalId = null;
42
+ }
43
+ this.pc = null;
44
+ this.onStats = null;
45
+ }
46
+ isRunning() {
47
+ return this.intervalId !== null;
48
+ }
49
+ async collect() {
50
+ if (!this.pc || !this.onStats) return;
51
+ try {
52
+ const rawStats = await this.pc.getStats();
53
+ const stats = this.parse(rawStats);
54
+ this.onStats(stats);
55
+ } catch {
56
+ this.stop();
57
+ }
58
+ }
59
+ parse(rawStats) {
60
+ const now = performance.now();
61
+ const elapsed = this.prevTimestamp > 0 ? (now - this.prevTimestamp) / 1e3 : 0;
62
+ let video = null;
63
+ let audio = null;
64
+ let outboundVideo = null;
65
+ const connection = {
66
+ currentRoundTripTime: null,
67
+ availableOutgoingBitrate: null
68
+ };
69
+ rawStats.forEach((report) => {
70
+ if (report.type === "inbound-rtp" && report.kind === "video") {
71
+ const bytesReceived = report.bytesReceived ?? 0;
72
+ const bitrate = elapsed > 0 ? (bytesReceived - this.prevBytesVideo) * 8 / elapsed : 0;
73
+ this.prevBytesVideo = bytesReceived;
74
+ const r = report;
75
+ const packetsLost = r.packetsLost ?? 0;
76
+ const framesDropped = r.framesDropped ?? 0;
77
+ const freezeCount = r.freezeCount ?? 0;
78
+ const freezeDuration = r.totalFreezesDuration ?? 0;
79
+ video = {
80
+ framesDecoded: r.framesDecoded ?? 0,
81
+ framesDropped,
82
+ framesPerSecond: r.framesPerSecond ?? 0,
83
+ frameWidth: r.frameWidth ?? 0,
84
+ frameHeight: r.frameHeight ?? 0,
85
+ bytesReceived,
86
+ packetsReceived: r.packetsReceived ?? 0,
87
+ packetsLost,
88
+ jitter: r.jitter ?? 0,
89
+ bitrate: Math.round(bitrate),
90
+ freezeCount,
91
+ totalFreezesDuration: freezeDuration,
92
+ packetsLostDelta: Math.max(0, packetsLost - this.prevPacketsLostVideo),
93
+ framesDroppedDelta: Math.max(0, framesDropped - this.prevFramesDropped),
94
+ freezeCountDelta: Math.max(0, freezeCount - this.prevFreezeCount),
95
+ freezeDurationDelta: Math.max(0, freezeDuration - this.prevFreezeDuration)
96
+ };
97
+ this.prevPacketsLostVideo = packetsLost;
98
+ this.prevFramesDropped = framesDropped;
99
+ this.prevFreezeCount = freezeCount;
100
+ this.prevFreezeDuration = freezeDuration;
101
+ }
102
+ if (report.type === "outbound-rtp" && report.kind === "video") {
103
+ const r = report;
104
+ const bytesSent = r.bytesSent ?? 0;
105
+ const outBitrate = elapsed > 0 ? (bytesSent - this.prevBytesSentVideo) * 8 / elapsed : 0;
106
+ this.prevBytesSentVideo = bytesSent;
107
+ outboundVideo = {
108
+ qualityLimitationReason: r.qualityLimitationReason ?? "none",
109
+ qualityLimitationDurations: r.qualityLimitationDurations ?? {},
110
+ bytesSent,
111
+ packetsSent: r.packetsSent ?? 0,
112
+ framesPerSecond: r.framesPerSecond ?? 0,
113
+ frameWidth: r.frameWidth ?? 0,
114
+ frameHeight: r.frameHeight ?? 0,
115
+ bitrate: Math.round(outBitrate)
116
+ };
117
+ }
118
+ if (report.type === "inbound-rtp" && report.kind === "audio") {
119
+ const bytesReceived = report.bytesReceived ?? 0;
120
+ const bitrate = elapsed > 0 ? (bytesReceived - this.prevBytesAudio) * 8 / elapsed : 0;
121
+ this.prevBytesAudio = bytesReceived;
122
+ const r = report;
123
+ const audioPacketsLost = r.packetsLost ?? 0;
124
+ audio = {
125
+ bytesReceived,
126
+ packetsReceived: r.packetsReceived ?? 0,
127
+ packetsLost: audioPacketsLost,
128
+ jitter: r.jitter ?? 0,
129
+ bitrate: Math.round(bitrate),
130
+ packetsLostDelta: Math.max(0, audioPacketsLost - this.prevPacketsLostAudio)
131
+ };
132
+ this.prevPacketsLostAudio = audioPacketsLost;
133
+ }
134
+ if (report.type === "candidate-pair") {
135
+ const r = report;
136
+ if (r.state === "succeeded") {
137
+ connection.currentRoundTripTime = r.currentRoundTripTime ?? null;
138
+ connection.availableOutgoingBitrate = r.availableOutgoingBitrate ?? null;
139
+ }
140
+ }
141
+ });
142
+ this.prevTimestamp = now;
143
+ return {
144
+ timestamp: Date.now(),
145
+ video,
146
+ audio,
147
+ outboundVideo,
148
+ connection
149
+ };
150
+ }
151
+ };
152
+
153
+ //#endregion
154
+ export { WebRTCStatsCollector };
@@ -6,6 +6,7 @@ declare const modelStateSchema: z.ZodObject<{
6
6
  text: z.ZodString;
7
7
  enhance: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
8
8
  }, z.core.$strip>>;
9
+ image: z.ZodOptional<z.ZodUnion<readonly [z.ZodCustom<Blob, Blob>, z.ZodCustom<File, File>, z.ZodString]>>;
9
10
  }, z.core.$strip>;
10
11
  type ModelState = z.infer<typeof modelStateSchema>;
11
12
  //#endregion
@@ -1,10 +1,17 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  //#region src/shared/types.ts
4
- const modelStateSchema = z.object({ prompt: z.object({
5
- text: z.string().min(1),
6
- enhance: z.boolean().optional().default(true)
7
- }).optional() });
4
+ const modelStateSchema = z.object({
5
+ prompt: z.object({
6
+ text: z.string().min(1),
7
+ enhance: z.boolean().optional().default(true)
8
+ }).optional(),
9
+ image: z.union([
10
+ z.instanceof(Blob),
11
+ z.instanceof(File),
12
+ z.string()
13
+ ]).optional()
14
+ });
8
15
 
9
16
  //#endregion
10
17
  export { modelStateSchema };
@@ -8,7 +8,6 @@ type DecartSDKError = {
8
8
  declare const ERROR_CODES: {
9
9
  readonly INVALID_API_KEY: "INVALID_API_KEY";
10
10
  readonly INVALID_BASE_URL: "INVALID_BASE_URL";
11
- readonly WEB_RTC_ERROR: "WEB_RTC_ERROR";
12
11
  readonly PROCESSING_ERROR: "PROCESSING_ERROR";
13
12
  readonly INVALID_INPUT: "INVALID_INPUT";
14
13
  readonly INVALID_OPTIONS: "INVALID_OPTIONS";
@@ -18,6 +17,11 @@ declare const ERROR_CODES: {
18
17
  readonly QUEUE_RESULT_ERROR: "QUEUE_RESULT_ERROR";
19
18
  readonly JOB_NOT_COMPLETED: "JOB_NOT_COMPLETED";
20
19
  readonly TOKEN_CREATE_ERROR: "TOKEN_CREATE_ERROR";
20
+ readonly WEBRTC_WEBSOCKET_ERROR: "WEBRTC_WEBSOCKET_ERROR";
21
+ readonly WEBRTC_ICE_ERROR: "WEBRTC_ICE_ERROR";
22
+ readonly WEBRTC_TIMEOUT_ERROR: "WEBRTC_TIMEOUT_ERROR";
23
+ readonly WEBRTC_SERVER_ERROR: "WEBRTC_SERVER_ERROR";
24
+ readonly WEBRTC_SIGNALING_ERROR: "WEBRTC_SIGNALING_ERROR";
21
25
  };
22
26
  //#endregion
23
27
  export { DecartSDKError, ERROR_CODES };