@decartai/sdk 0.1.5 → 0.1.7
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 +64 -0
- package/dist/index.d.ts +28 -2
- package/dist/index.js +7 -1
- package/dist/realtime/client.d.ts +8 -1
- package/dist/realtime/client.js +14 -3
- package/dist/realtime/config-realtime.js +56 -1
- package/dist/realtime/media-channel.js +1 -0
- package/dist/realtime/mirror-stream.js +41 -31
- package/dist/realtime/observability/connection-quality.d.ts +54 -0
- package/dist/realtime/observability/connection-quality.js +219 -0
- package/dist/realtime/observability/glass-to-glass.d.ts +41 -0
- package/dist/realtime/observability/glass-to-glass.js +229 -0
- package/dist/realtime/observability/pixel-marker.js +144 -0
- package/dist/realtime/observability/realtime-observability.js +51 -1
- package/dist/realtime/observability/telemetry-reporter.js +16 -9
- package/dist/realtime/observability/webrtc-stats.d.ts +9 -0
- package/dist/realtime/observability/webrtc-stats.js +2 -1
- package/dist/realtime/preflight.d.ts +59 -0
- package/dist/realtime/preflight.js +311 -0
- package/dist/realtime/signaling-channel.js +63 -32
- package/dist/realtime/stream-session.js +11 -5
- package/package.json +1 -1
- package/dist/realtime/initial-state-gate.js +0 -21
|
@@ -3,10 +3,43 @@ import { createConsoleLogger } from "../utils/logger.js";
|
|
|
3
3
|
import { REALTIME_CONFIG } from "./config-realtime.js";
|
|
4
4
|
import mitt from "mitt";
|
|
5
5
|
//#region src/realtime/signaling-channel.ts
|
|
6
|
+
function buildInitialStateRequest(initialState) {
|
|
7
|
+
if (!initialState) return null;
|
|
8
|
+
if (initialState.imageRef !== void 0 || initialState.image !== void 0) {
|
|
9
|
+
const message = initialState.imageRef !== void 0 ? {
|
|
10
|
+
type: "set_image",
|
|
11
|
+
image_ref: initialState.imageRef
|
|
12
|
+
} : {
|
|
13
|
+
type: "set_image",
|
|
14
|
+
image_data: initialState.image ?? null
|
|
15
|
+
};
|
|
16
|
+
if (initialState.prompt !== void 0) message.prompt = initialState.prompt;
|
|
17
|
+
if (initialState.enhance !== void 0) message.enhance_prompt = initialState.enhance;
|
|
18
|
+
return {
|
|
19
|
+
message,
|
|
20
|
+
matchAck: (msg) => msg.type === "set_image_ack",
|
|
21
|
+
label: "Image send"
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (initialState.prompt !== void 0 && initialState.prompt !== null) {
|
|
25
|
+
const text = initialState.prompt;
|
|
26
|
+
return {
|
|
27
|
+
message: {
|
|
28
|
+
type: "prompt",
|
|
29
|
+
prompt: text,
|
|
30
|
+
enhance_prompt: initialState.enhance ?? true
|
|
31
|
+
},
|
|
32
|
+
matchAck: (msg) => msg.type === "prompt_ack" && msg.prompt === text,
|
|
33
|
+
label: "Prompt send"
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
6
38
|
var SignalingChannel = class {
|
|
7
39
|
ws = null;
|
|
8
40
|
events = mitt();
|
|
9
41
|
pendingAcks = [];
|
|
42
|
+
bufferedAcks = [];
|
|
10
43
|
pendingRoomInfo = null;
|
|
11
44
|
connected = false;
|
|
12
45
|
closing = false;
|
|
@@ -29,7 +62,12 @@ var SignalingChannel = class {
|
|
|
29
62
|
this.config.observability?.endPhase("websocket-open", { success: true });
|
|
30
63
|
this.config.observability?.startPhase("room-join");
|
|
31
64
|
const roomInfoWait = this.waitForRoomInfo(handshakeTimeout);
|
|
32
|
-
|
|
65
|
+
const initialStateRequest = buildInitialStateRequest(opts.initialState);
|
|
66
|
+
const joinMessage = {
|
|
67
|
+
type: "livekit_join",
|
|
68
|
+
initial_state: initialStateRequest ? initialStateRequest.message : null
|
|
69
|
+
};
|
|
70
|
+
if (!this.writeMessage(joinMessage)) {
|
|
33
71
|
roomInfoWait.cancel();
|
|
34
72
|
throw new Error("WebSocket is not open");
|
|
35
73
|
}
|
|
@@ -42,18 +80,24 @@ var SignalingChannel = class {
|
|
|
42
80
|
}
|
|
43
81
|
this.config.observability?.endPhase("room-join", { success: true });
|
|
44
82
|
this.connected = true;
|
|
45
|
-
const initialStateAck = this.
|
|
83
|
+
const initialStateAck = initialStateRequest ? this.flushInitialState(initialStateRequest) : Promise.resolve();
|
|
46
84
|
initialStateAck.catch(() => {});
|
|
47
85
|
return {
|
|
48
86
|
roomInfo,
|
|
49
87
|
initialStateAck
|
|
50
88
|
};
|
|
51
89
|
}
|
|
52
|
-
async
|
|
53
|
-
if (!initialState) return;
|
|
90
|
+
async flushInitialState(request) {
|
|
54
91
|
this.config.observability?.startPhase("initial-state-handshake");
|
|
55
|
-
await this.
|
|
92
|
+
const ack = await this.request({
|
|
93
|
+
message: request.message,
|
|
94
|
+
matchAck: request.matchAck,
|
|
95
|
+
timeoutMs: REALTIME_CONFIG.signaling.requestTimeoutMs,
|
|
96
|
+
label: request.label,
|
|
97
|
+
write: false
|
|
98
|
+
});
|
|
56
99
|
this.config.observability?.endPhase("initial-state-handshake", { success: true });
|
|
100
|
+
if (!ack.success) throw new Error(ack.error ?? `Failed: ${request.label}`);
|
|
57
101
|
}
|
|
58
102
|
close() {
|
|
59
103
|
this.closing = true;
|
|
@@ -180,32 +224,14 @@ var SignalingChannel = class {
|
|
|
180
224
|
cancel: cleanup
|
|
181
225
|
};
|
|
182
226
|
}
|
|
183
|
-
async
|
|
184
|
-
if (!initialState) return;
|
|
185
|
-
if (initialState.imageRef !== void 0) {
|
|
186
|
-
await this.setImage({
|
|
187
|
-
kind: "ref",
|
|
188
|
-
ref: initialState.imageRef
|
|
189
|
-
}, {
|
|
190
|
-
prompt: initialState.prompt,
|
|
191
|
-
enhance: initialState.enhance
|
|
192
|
-
});
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
if (initialState.image !== void 0) {
|
|
196
|
-
await this.setImage({
|
|
197
|
-
kind: "data",
|
|
198
|
-
data: initialState.image
|
|
199
|
-
}, {
|
|
200
|
-
prompt: initialState.prompt,
|
|
201
|
-
enhance: initialState.enhance
|
|
202
|
-
});
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (initialState.prompt !== void 0 && initialState.prompt !== null) await this.sendPrompt(initialState.prompt, { enhance: initialState.enhance });
|
|
206
|
-
}
|
|
207
|
-
async request({ message, matchAck, timeoutMs, label }) {
|
|
227
|
+
async request({ message, matchAck, timeoutMs, label, write = true }) {
|
|
208
228
|
return new Promise((resolve, reject) => {
|
|
229
|
+
const buffered = this.bufferedAcks.findIndex((m) => matchAck(m));
|
|
230
|
+
if (buffered !== -1) {
|
|
231
|
+
const [claimed] = this.bufferedAcks.splice(buffered, 1);
|
|
232
|
+
resolve(claimed);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
209
235
|
const timer = setTimeout(() => {
|
|
210
236
|
cleanup();
|
|
211
237
|
this.logger.warn("signaling: ack timed out", {
|
|
@@ -230,7 +256,7 @@ var SignalingChannel = class {
|
|
|
230
256
|
this.pendingAcks = this.pendingAcks.filter((e) => e !== entry);
|
|
231
257
|
};
|
|
232
258
|
this.pendingAcks.push(entry);
|
|
233
|
-
if (!this.writeMessage(message)) {
|
|
259
|
+
if (write && !this.writeMessage(message)) {
|
|
234
260
|
cleanup();
|
|
235
261
|
reject(/* @__PURE__ */ new Error("WebSocket is not open"));
|
|
236
262
|
}
|
|
@@ -244,7 +270,11 @@ var SignalingChannel = class {
|
|
|
244
270
|
handleMessage(msg) {
|
|
245
271
|
for (const ack of [...this.pendingAcks]) if (ack.matches(msg)) {
|
|
246
272
|
ack.onMatch(msg);
|
|
247
|
-
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (!this.connected && (msg.type === "set_image_ack" || msg.type === "prompt_ack")) {
|
|
276
|
+
this.bufferedAcks.push(msg);
|
|
277
|
+
return;
|
|
248
278
|
}
|
|
249
279
|
switch (msg.type) {
|
|
250
280
|
case "livekit_room_info":
|
|
@@ -298,6 +328,7 @@ var SignalingChannel = class {
|
|
|
298
328
|
rejectAllPending(error) {
|
|
299
329
|
const pending = this.pendingAcks;
|
|
300
330
|
this.pendingAcks = [];
|
|
331
|
+
this.bufferedAcks = [];
|
|
301
332
|
for (const entry of pending) entry.reject(error);
|
|
302
333
|
}
|
|
303
334
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { createConsoleLogger } from "../utils/logger.js";
|
|
2
2
|
import { REALTIME_CONFIG } from "./config-realtime.js";
|
|
3
|
-
import { InitialStateGate } from "./initial-state-gate.js";
|
|
4
3
|
import { MediaChannel } from "./media-channel.js";
|
|
5
4
|
import { SignalingChannel } from "./signaling-channel.js";
|
|
6
5
|
import mitt from "mitt";
|
|
@@ -25,7 +24,7 @@ var StreamSession = class {
|
|
|
25
24
|
queue = null;
|
|
26
25
|
disposed = false;
|
|
27
26
|
currentAttempt = 0;
|
|
28
|
-
|
|
27
|
+
teardownGeneration = 0;
|
|
29
28
|
logger;
|
|
30
29
|
constructor(config) {
|
|
31
30
|
this.config = config;
|
|
@@ -101,11 +100,12 @@ var StreamSession = class {
|
|
|
101
100
|
this.resetHandshakeState();
|
|
102
101
|
const initialState = this.getInitialState();
|
|
103
102
|
this.config.observability?.beginConnectionBreakdown(attempt, getInitialImageSizeKb(initialState?.image));
|
|
104
|
-
|
|
103
|
+
this.config.observability?.markGlassToGlassStart();
|
|
105
104
|
const { roomInfo, initialStateAck } = await this.signaling.openAndJoin({
|
|
106
105
|
connectTimeout: REALTIME_CONFIG.session.connectionTimeoutMs,
|
|
107
106
|
initialState
|
|
108
107
|
});
|
|
108
|
+
this.watchInitialStateAck(initialStateAck, attempt);
|
|
109
109
|
if (this.disposed || this.currentAttempt !== attempt) {
|
|
110
110
|
this.tearDown();
|
|
111
111
|
throw new AbortError("Stale connect attempt");
|
|
@@ -116,7 +116,6 @@ var StreamSession = class {
|
|
|
116
116
|
url: roomInfo.livekitUrl,
|
|
117
117
|
token: roomInfo.token
|
|
118
118
|
});
|
|
119
|
-
if (!await gateAttempt.waitForReadiness(initialStateAck)) throw new AbortError("Stale connect attempt");
|
|
120
119
|
await this.media.publishLocalTracks();
|
|
121
120
|
} catch (error) {
|
|
122
121
|
this.tearDown();
|
|
@@ -140,6 +139,13 @@ var StreamSession = class {
|
|
|
140
139
|
throw error;
|
|
141
140
|
}
|
|
142
141
|
}
|
|
142
|
+
watchInitialStateAck(initialStateAck, attempt) {
|
|
143
|
+
const generation = this.teardownGeneration;
|
|
144
|
+
initialStateAck.catch((error) => {
|
|
145
|
+
if (this.disposed || this.currentAttempt !== attempt || this.teardownGeneration !== generation) return;
|
|
146
|
+
this.events.emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
147
|
+
});
|
|
148
|
+
}
|
|
143
149
|
getInitialState() {
|
|
144
150
|
if (this.config.initialImageRef !== void 0) return {
|
|
145
151
|
imageRef: this.config.initialImageRef,
|
|
@@ -239,9 +245,9 @@ var StreamSession = class {
|
|
|
239
245
|
this.wireMediaEvents();
|
|
240
246
|
}
|
|
241
247
|
tearDown() {
|
|
248
|
+
this.teardownGeneration++;
|
|
242
249
|
this.signaling.close();
|
|
243
250
|
this.media.disconnect();
|
|
244
|
-
this.initialStateGate.reset();
|
|
245
251
|
this.resetHandshakeState();
|
|
246
252
|
}
|
|
247
253
|
resetHandshakeState() {
|
package/package.json
CHANGED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
//#region src/realtime/initial-state-gate.ts
|
|
2
|
-
var InitialStateGate = class {
|
|
3
|
-
attemptId = 0;
|
|
4
|
-
startAttempt(initialState) {
|
|
5
|
-
const attemptId = ++this.attemptId;
|
|
6
|
-
const shouldWait = hasCallerProvidedInitialState(initialState);
|
|
7
|
-
return { waitForReadiness: async (initialStateAck) => {
|
|
8
|
-
if (shouldWait) await initialStateAck;
|
|
9
|
-
return this.attemptId === attemptId;
|
|
10
|
-
} };
|
|
11
|
-
}
|
|
12
|
-
reset() {
|
|
13
|
-
this.attemptId++;
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
function hasCallerProvidedInitialState(state) {
|
|
17
|
-
if (!state) return false;
|
|
18
|
-
return state.image !== void 0 && state.image !== null || state.prompt !== void 0 && state.prompt !== null;
|
|
19
|
-
}
|
|
20
|
-
//#endregion
|
|
21
|
-
export { InitialStateGate };
|