@decartai/sdk 0.0.68 → 0.1.1
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 +49 -9
- package/dist/files/client.d.ts +33 -0
- package/dist/files/client.js +65 -0
- package/dist/files/types.d.ts +22 -0
- package/dist/files/types.js +4 -0
- package/dist/index.d.ts +19 -4
- package/dist/index.js +50 -28
- package/dist/process/client.js +1 -3
- package/dist/process/request.js +1 -3
- package/dist/queue/client.js +1 -3
- package/dist/queue/polling.js +1 -2
- package/dist/queue/request.js +1 -3
- package/dist/realtime/client.d.ts +23 -11
- package/dist/realtime/client.js +85 -156
- package/dist/realtime/config-realtime.js +49 -0
- package/dist/realtime/event-buffer.js +1 -3
- package/dist/realtime/initial-state-gate.js +21 -0
- package/dist/realtime/media-channel.js +82 -0
- package/dist/realtime/methods.js +25 -43
- package/dist/realtime/mirror-stream.js +1 -2
- package/dist/realtime/observability/diagnostics.d.ts +14 -53
- package/dist/realtime/observability/livekit-stats-provider.js +25 -0
- package/dist/realtime/observability/realtime-observability.js +70 -6
- package/dist/realtime/observability/telemetry-reporter.js +9 -28
- package/dist/realtime/observability/webrtc-stats.d.ts +5 -4
- package/dist/realtime/observability/webrtc-stats.js +3 -5
- package/dist/realtime/signaling-channel.js +302 -0
- package/dist/realtime/stream-session.js +257 -0
- package/dist/realtime/subscribe-client.d.ts +2 -3
- package/dist/realtime/subscribe-client.js +115 -11
- package/dist/realtime/types.d.ts +25 -1
- package/dist/shared/model.d.ts +11 -1
- package/dist/shared/model.js +51 -14
- package/dist/shared/request.js +1 -3
- package/dist/shared/types.js +1 -3
- package/dist/tokens/client.js +1 -3
- package/dist/utils/env.js +1 -2
- package/dist/utils/errors.d.ts +3 -0
- package/dist/utils/errors.js +4 -2
- package/dist/utils/logger.js +1 -2
- package/dist/utils/media.js +43 -0
- package/dist/utils/platform.js +13 -0
- package/dist/utils/user-agent.js +1 -3
- package/dist/version.js +1 -2
- package/package.json +2 -1
- package/dist/realtime/webrtc-connection.js +0 -500
- package/dist/realtime/webrtc-manager.js +0 -210
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { createConsoleLogger } from "../utils/logger.js";
|
|
2
|
+
import { REALTIME_CONFIG } from "./config-realtime.js";
|
|
3
|
+
import { InitialStateGate } from "./initial-state-gate.js";
|
|
4
|
+
import { MediaChannel } from "./media-channel.js";
|
|
5
|
+
import { SignalingChannel } from "./signaling-channel.js";
|
|
6
|
+
import mitt from "mitt";
|
|
7
|
+
import pRetry, { AbortError } from "p-retry";
|
|
8
|
+
//#region src/realtime/stream-session.ts
|
|
9
|
+
function encodeSubscribeToken(roomName) {
|
|
10
|
+
return btoa(JSON.stringify({ room_name: roomName }));
|
|
11
|
+
}
|
|
12
|
+
function getInitialImageSizeKb(image) {
|
|
13
|
+
if (!image) return null;
|
|
14
|
+
const commaIdx = image.indexOf(",");
|
|
15
|
+
const base64 = commaIdx >= 0 && image.startsWith("data:") ? image.slice(commaIdx + 1) : image;
|
|
16
|
+
const padding = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
|
|
17
|
+
const bytes = Math.floor(base64.length * 3 / 4) - padding;
|
|
18
|
+
return Math.max(0, Math.round(bytes / 1024));
|
|
19
|
+
}
|
|
20
|
+
var StreamSession = class {
|
|
21
|
+
signaling;
|
|
22
|
+
media;
|
|
23
|
+
events = mitt();
|
|
24
|
+
state = "disconnected";
|
|
25
|
+
queue = null;
|
|
26
|
+
disposed = false;
|
|
27
|
+
currentAttempt = 0;
|
|
28
|
+
initialStateGate = new InitialStateGate();
|
|
29
|
+
logger;
|
|
30
|
+
constructor(config) {
|
|
31
|
+
this.config = config;
|
|
32
|
+
this.logger = config.logger ?? createConsoleLogger("warn");
|
|
33
|
+
this.createTransport();
|
|
34
|
+
}
|
|
35
|
+
on(event, handler) {
|
|
36
|
+
this.events.on(event, handler);
|
|
37
|
+
}
|
|
38
|
+
off(event, handler) {
|
|
39
|
+
this.events.off(event, handler);
|
|
40
|
+
}
|
|
41
|
+
getStatus() {
|
|
42
|
+
return {
|
|
43
|
+
connection: this.state,
|
|
44
|
+
queue: this.queue
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
getConnectionState() {
|
|
48
|
+
return this.state;
|
|
49
|
+
}
|
|
50
|
+
isConnected() {
|
|
51
|
+
return this.state === "connected" || this.state === "generating";
|
|
52
|
+
}
|
|
53
|
+
async connect() {
|
|
54
|
+
this.disposed = false;
|
|
55
|
+
const attempt = ++this.currentAttempt;
|
|
56
|
+
this.setState("connecting");
|
|
57
|
+
this.logger.info("realtime connect: starting", { attemptCycle: attempt });
|
|
58
|
+
try {
|
|
59
|
+
await pRetry(() => this.runOneConnect(attempt), this.retryOptionsFor(attempt));
|
|
60
|
+
} catch (error) {
|
|
61
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
62
|
+
this.logger.error("realtime connect: exhausted all retries", { error: message });
|
|
63
|
+
if (this.currentAttempt === attempt && !this.disposed) this.setState("disconnected");
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async sendPrompt(text, opts) {
|
|
68
|
+
this.assertConnected();
|
|
69
|
+
return this.signaling.sendPrompt(text, opts);
|
|
70
|
+
}
|
|
71
|
+
async setImage(payload, opts) {
|
|
72
|
+
this.assertConnected();
|
|
73
|
+
return this.signaling.setImage(payload, opts);
|
|
74
|
+
}
|
|
75
|
+
disconnect() {
|
|
76
|
+
this.disposed = true;
|
|
77
|
+
this.tearDown();
|
|
78
|
+
this.setState("disconnected");
|
|
79
|
+
}
|
|
80
|
+
assertConnected() {
|
|
81
|
+
if (!this.isConnected()) throw new Error(`Cannot send message: connection is ${this.state}`);
|
|
82
|
+
}
|
|
83
|
+
retryOptionsFor(attempt) {
|
|
84
|
+
return {
|
|
85
|
+
...REALTIME_CONFIG.session.retry,
|
|
86
|
+
onFailedAttempt: (_error) => {
|
|
87
|
+
this.tearDown();
|
|
88
|
+
},
|
|
89
|
+
shouldRetry: (error) => {
|
|
90
|
+
if (this.disposed || this.currentAttempt !== attempt) return false;
|
|
91
|
+
const msg = error.message.toLowerCase();
|
|
92
|
+
const permanent = REALTIME_CONFIG.session.permanentErrorSubstrings.some((err) => msg.includes(err));
|
|
93
|
+
if (permanent) this.logger.error("realtime connect: permanent error, not retrying", { error: error.message });
|
|
94
|
+
return !permanent;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
async runOneConnect(attempt) {
|
|
99
|
+
if (this.disposed || this.currentAttempt !== attempt) throw new AbortError("Stale connect attempt");
|
|
100
|
+
try {
|
|
101
|
+
this.resetHandshakeState();
|
|
102
|
+
const initialState = this.getInitialState();
|
|
103
|
+
this.config.observability?.beginConnectionBreakdown(attempt, getInitialImageSizeKb(initialState?.image));
|
|
104
|
+
const gateAttempt = this.initialStateGate.startAttempt(initialState);
|
|
105
|
+
const { roomInfo, initialStateAck } = await this.signaling.openAndJoin({
|
|
106
|
+
connectTimeout: REALTIME_CONFIG.session.connectionTimeoutMs,
|
|
107
|
+
initialState
|
|
108
|
+
});
|
|
109
|
+
if (this.disposed || this.currentAttempt !== attempt) {
|
|
110
|
+
this.tearDown();
|
|
111
|
+
throw new AbortError("Stale connect attempt");
|
|
112
|
+
}
|
|
113
|
+
this.queue = null;
|
|
114
|
+
try {
|
|
115
|
+
await this.media.connect({
|
|
116
|
+
url: roomInfo.livekitUrl,
|
|
117
|
+
token: roomInfo.token
|
|
118
|
+
});
|
|
119
|
+
if (!await gateAttempt.waitForReadiness(initialStateAck)) throw new AbortError("Stale connect attempt");
|
|
120
|
+
await this.media.publishLocalTracks();
|
|
121
|
+
} catch (error) {
|
|
122
|
+
this.tearDown();
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
if (this.disposed || this.currentAttempt !== attempt) {
|
|
126
|
+
this.tearDown();
|
|
127
|
+
throw new AbortError("Stale connect attempt");
|
|
128
|
+
}
|
|
129
|
+
this.config.observability?.finishConnectionBreakdown({ success: true });
|
|
130
|
+
this.setState("connected");
|
|
131
|
+
this.events.emit("sessionStarted", {
|
|
132
|
+
sessionId: roomInfo.sessionId,
|
|
133
|
+
subscribeToken: encodeSubscribeToken(roomInfo.roomName)
|
|
134
|
+
});
|
|
135
|
+
} catch (error) {
|
|
136
|
+
this.config.observability?.finishConnectionBreakdown({
|
|
137
|
+
success: false,
|
|
138
|
+
error: error instanceof Error ? error.message : String(error)
|
|
139
|
+
});
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
getInitialState() {
|
|
144
|
+
if (this.config.initialImageRef !== void 0) return {
|
|
145
|
+
imageRef: this.config.initialImageRef,
|
|
146
|
+
prompt: this.config.initialPrompt?.text,
|
|
147
|
+
enhance: this.config.initialPrompt?.enhance
|
|
148
|
+
};
|
|
149
|
+
if (this.config.initialImage !== void 0) return {
|
|
150
|
+
image: this.config.initialImage,
|
|
151
|
+
prompt: this.config.initialPrompt?.text,
|
|
152
|
+
enhance: this.config.initialPrompt?.enhance
|
|
153
|
+
};
|
|
154
|
+
if (this.config.initialPrompt) return {
|
|
155
|
+
prompt: this.config.initialPrompt.text,
|
|
156
|
+
enhance: this.config.initialPrompt.enhance
|
|
157
|
+
};
|
|
158
|
+
if (this.config.localStream) return {
|
|
159
|
+
image: null,
|
|
160
|
+
prompt: null
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
wireSignalingEvents() {
|
|
164
|
+
this.signaling.on("queuePosition", (qp) => {
|
|
165
|
+
this.queue = qp;
|
|
166
|
+
this.events.emit("queuePosition", qp);
|
|
167
|
+
});
|
|
168
|
+
this.signaling.on("generationTick", (e) => this.events.emit("generationTick", e));
|
|
169
|
+
this.signaling.on("generationEnded", (e) => this.events.emit("generationEnded", e));
|
|
170
|
+
this.signaling.on("serverError", (err) => this.events.emit("error", err));
|
|
171
|
+
this.signaling.on("closed", (info) => this.handleConnectionLoss({
|
|
172
|
+
source: "signaling",
|
|
173
|
+
...info
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
wireMediaEvents() {
|
|
177
|
+
this.media.on("remoteStream", (stream) => this.events.emit("remoteStream", stream));
|
|
178
|
+
this.media.on("firstFrame", () => {
|
|
179
|
+
if (this.state === "connected") this.setState("generating");
|
|
180
|
+
});
|
|
181
|
+
this.media.on("disconnected", (info) => this.handleConnectionLoss({
|
|
182
|
+
source: "media",
|
|
183
|
+
reason: info.reason
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
handleConnectionLoss(cause) {
|
|
187
|
+
if (this.disposed) return;
|
|
188
|
+
if (this.state !== "connected" && this.state !== "generating") {
|
|
189
|
+
this.logger.debug("connection loss ignored (not connected)", {
|
|
190
|
+
state: this.state,
|
|
191
|
+
...cause
|
|
192
|
+
});
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
this.logger.warn("realtime connection lost; scheduling reconnect", {
|
|
196
|
+
state: this.state,
|
|
197
|
+
...cause
|
|
198
|
+
});
|
|
199
|
+
this.scheduleReconnect();
|
|
200
|
+
}
|
|
201
|
+
scheduleReconnect() {
|
|
202
|
+
const attempt = ++this.currentAttempt;
|
|
203
|
+
this.setState("reconnecting");
|
|
204
|
+
pRetry(async () => {
|
|
205
|
+
if (this.disposed || this.currentAttempt !== attempt) throw new AbortError("Reconnect cancelled");
|
|
206
|
+
this.tearDown();
|
|
207
|
+
this.createTransport();
|
|
208
|
+
await this.runOneConnect(attempt);
|
|
209
|
+
}, this.retryOptionsFor(attempt)).then(() => {
|
|
210
|
+
if (this.disposed || this.currentAttempt !== attempt) return;
|
|
211
|
+
this.logger.info("realtime reconnect: succeeded");
|
|
212
|
+
}).catch((error) => {
|
|
213
|
+
if (this.disposed || this.currentAttempt !== attempt) return;
|
|
214
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
215
|
+
this.logger.error("realtime reconnect: failed permanently", { error: message });
|
|
216
|
+
this.tearDown();
|
|
217
|
+
this.setState("disconnected");
|
|
218
|
+
this.events.emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
createTransport() {
|
|
222
|
+
this.signaling = new SignalingChannel({
|
|
223
|
+
url: this.config.url,
|
|
224
|
+
integration: this.config.integration,
|
|
225
|
+
logger: this.logger,
|
|
226
|
+
observability: this.config.observability
|
|
227
|
+
});
|
|
228
|
+
this.media = new MediaChannel({
|
|
229
|
+
observability: this.config.observability,
|
|
230
|
+
localStream: this.config.localStream,
|
|
231
|
+
logger: this.logger,
|
|
232
|
+
videoCodec: this.config.videoCodec
|
|
233
|
+
});
|
|
234
|
+
this.wireSignalingEvents();
|
|
235
|
+
this.wireMediaEvents();
|
|
236
|
+
}
|
|
237
|
+
tearDown() {
|
|
238
|
+
this.signaling.close();
|
|
239
|
+
this.media.disconnect();
|
|
240
|
+
this.initialStateGate.reset();
|
|
241
|
+
this.resetHandshakeState();
|
|
242
|
+
}
|
|
243
|
+
resetHandshakeState() {
|
|
244
|
+
this.queue = null;
|
|
245
|
+
}
|
|
246
|
+
setState(state) {
|
|
247
|
+
if (this.state === state) return;
|
|
248
|
+
this.logger.debug("realtime state change", {
|
|
249
|
+
from: this.state,
|
|
250
|
+
to: state
|
|
251
|
+
});
|
|
252
|
+
this.state = state;
|
|
253
|
+
this.events.emit("connectionChange", state);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
//#endregion
|
|
257
|
+
export { StreamSession };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { DecartSDKError } from "../utils/errors.js";
|
|
2
|
-
import { ConnectionState } from "./types.js";
|
|
3
2
|
import { DiagnosticEvent } from "./observability/diagnostics.js";
|
|
4
|
-
import {
|
|
3
|
+
import { ConnectionState } from "./types.js";
|
|
5
4
|
|
|
6
5
|
//#region src/realtime/subscribe-client.d.ts
|
|
7
6
|
|
|
@@ -9,7 +8,6 @@ type SubscribeEvents = {
|
|
|
9
8
|
connectionChange: ConnectionState;
|
|
10
9
|
error: DecartSDKError;
|
|
11
10
|
diagnostic: DiagnosticEvent;
|
|
12
|
-
stats: WebRTCStats;
|
|
13
11
|
};
|
|
14
12
|
type RealTimeSubscribeClient = {
|
|
15
13
|
isConnected: () => boolean;
|
|
@@ -21,6 +19,7 @@ type RealTimeSubscribeClient = {
|
|
|
21
19
|
type SubscribeOptions = {
|
|
22
20
|
token: string;
|
|
23
21
|
onRemoteStream: (stream: MediaStream) => void;
|
|
22
|
+
onConnectionChange?: (state: ConnectionState) => void;
|
|
24
23
|
};
|
|
25
24
|
//#endregion
|
|
26
25
|
export { RealTimeSubscribeClient, SubscribeEvents, SubscribeOptions };
|
|
@@ -1,20 +1,124 @@
|
|
|
1
|
+
import { classifyWebrtcError } from "../utils/errors.js";
|
|
2
|
+
import { createConsoleLogger } from "../utils/logger.js";
|
|
3
|
+
import { createEventBuffer } from "./event-buffer.js";
|
|
4
|
+
import { REALTIME_CONFIG } from "./config-realtime.js";
|
|
5
|
+
import { RealtimeObservability } from "./observability/realtime-observability.js";
|
|
6
|
+
import { ConnectionState, Room, RoomEvent, Track } from "livekit-client";
|
|
1
7
|
//#region src/realtime/subscribe-client.ts
|
|
2
|
-
function encodeSubscribeToken(sessionId, serverIp, serverPort) {
|
|
3
|
-
return btoa(JSON.stringify({
|
|
4
|
-
sid: sessionId,
|
|
5
|
-
ip: serverIp,
|
|
6
|
-
port: serverPort
|
|
7
|
-
}));
|
|
8
|
-
}
|
|
9
8
|
function decodeSubscribeToken(token) {
|
|
10
9
|
try {
|
|
11
10
|
const payload = JSON.parse(atob(token));
|
|
12
|
-
if (!payload.
|
|
13
|
-
return payload;
|
|
11
|
+
if (!payload.room_name || typeof payload.room_name !== "string") throw new Error("Invalid subscribe token format");
|
|
12
|
+
return { room_name: payload.room_name };
|
|
14
13
|
} catch {
|
|
15
14
|
throw new Error("Invalid subscribe token");
|
|
16
15
|
}
|
|
17
16
|
}
|
|
18
|
-
|
|
17
|
+
function mapLiveKitState(state) {
|
|
18
|
+
switch (state) {
|
|
19
|
+
case ConnectionState.Connecting: return "connecting";
|
|
20
|
+
case ConnectionState.Connected: return "connected";
|
|
21
|
+
case ConnectionState.Reconnecting:
|
|
22
|
+
case ConnectionState.SignalReconnecting: return "reconnecting";
|
|
23
|
+
case ConnectionState.Disconnected: return "disconnected";
|
|
24
|
+
default: return "disconnected";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function fetchWatchStreamCredentials(opts) {
|
|
28
|
+
if (!/^https?:\/\//i.test(opts.baseUrl)) throw new Error(`watch-stream baseUrl must use http(s); got ${opts.baseUrl}`);
|
|
29
|
+
const url = `${opts.baseUrl}/watch-stream/${encodeURIComponent(opts.roomName)}`;
|
|
30
|
+
const res = await fetch(url, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: {
|
|
33
|
+
"x-api-key": opts.apiKey,
|
|
34
|
+
"content-type": "application/json"
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
const body = await res.text().catch(() => "");
|
|
39
|
+
throw new Error(`watch-stream request failed (${res.status}): ${body || res.statusText}`);
|
|
40
|
+
}
|
|
41
|
+
const json = await res.json();
|
|
42
|
+
if (!json.livekit_url || !json.token || !json.room_name) throw new Error("watch-stream response missing required fields");
|
|
43
|
+
return {
|
|
44
|
+
livekit_url: json.livekit_url,
|
|
45
|
+
token: json.token,
|
|
46
|
+
room_name: json.room_name
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const createRealTimeSubscribeClient = (opts) => {
|
|
50
|
+
const { baseUrl, apiKey, integration } = opts;
|
|
51
|
+
const logger = opts.logger ?? createConsoleLogger("info");
|
|
52
|
+
const subscribe = async (options) => {
|
|
53
|
+
const { room_name: roomName } = decodeSubscribeToken(options.token);
|
|
54
|
+
const { emitter, emitOrBuffer, flush, stop } = createEventBuffer();
|
|
55
|
+
let observability;
|
|
56
|
+
let room;
|
|
57
|
+
let currentState = "connecting";
|
|
58
|
+
let remoteStream = null;
|
|
59
|
+
const setState = (state) => {
|
|
60
|
+
if (currentState === state) return;
|
|
61
|
+
currentState = state;
|
|
62
|
+
options.onConnectionChange?.(state);
|
|
63
|
+
emitOrBuffer("connectionChange", state);
|
|
64
|
+
};
|
|
65
|
+
try {
|
|
66
|
+
observability = new RealtimeObservability({
|
|
67
|
+
telemetryEnabled: false,
|
|
68
|
+
apiKey,
|
|
69
|
+
integration,
|
|
70
|
+
logger,
|
|
71
|
+
onDiagnostic: (event) => emitOrBuffer("diagnostic", event)
|
|
72
|
+
});
|
|
73
|
+
setState("connecting");
|
|
74
|
+
const creds = await fetchWatchStreamCredentials({
|
|
75
|
+
baseUrl,
|
|
76
|
+
apiKey,
|
|
77
|
+
roomName
|
|
78
|
+
});
|
|
79
|
+
room = new Room(REALTIME_CONFIG.livekit.roomOptions);
|
|
80
|
+
const activeRoom = room;
|
|
81
|
+
activeRoom.on(RoomEvent.TrackSubscribed, (track, _pub, participant) => {
|
|
82
|
+
if (!participant.identity.startsWith(REALTIME_CONFIG.livekit.inferenceServerIdentityPrefix)) return;
|
|
83
|
+
if (track.kind !== Track.Kind.Video && track.kind !== Track.Kind.Audio) return;
|
|
84
|
+
track.attach();
|
|
85
|
+
const mediaStreamTrack = track.mediaStreamTrack;
|
|
86
|
+
if (!mediaStreamTrack) return;
|
|
87
|
+
remoteStream ??= new MediaStream();
|
|
88
|
+
if (!remoteStream.getTracks().includes(mediaStreamTrack)) remoteStream.addTrack(mediaStreamTrack);
|
|
89
|
+
options.onRemoteStream(remoteStream);
|
|
90
|
+
});
|
|
91
|
+
activeRoom.on(RoomEvent.ConnectionStateChanged, (state) => {
|
|
92
|
+
setState(mapLiveKitState(state));
|
|
93
|
+
});
|
|
94
|
+
activeRoom.on(RoomEvent.Disconnected, () => {
|
|
95
|
+
setState("disconnected");
|
|
96
|
+
});
|
|
97
|
+
await activeRoom.connect(creds.livekit_url, creds.token);
|
|
98
|
+
observability.setLiveKitRoom(activeRoom);
|
|
99
|
+
setState("connected");
|
|
100
|
+
const client = {
|
|
101
|
+
isConnected: () => activeRoom.state === ConnectionState.Connected,
|
|
102
|
+
getConnectionState: () => mapLiveKitState(activeRoom.state),
|
|
103
|
+
disconnect: () => {
|
|
104
|
+
observability?.stop();
|
|
105
|
+
stop();
|
|
106
|
+
activeRoom.disconnect().catch(() => {});
|
|
107
|
+
},
|
|
108
|
+
on: emitter.on,
|
|
109
|
+
off: emitter.off
|
|
110
|
+
};
|
|
111
|
+
flush();
|
|
112
|
+
return client;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
observability?.stop();
|
|
115
|
+
if (room) room.disconnect().catch(() => {});
|
|
116
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
117
|
+
logger.error("Realtime subscribe error", { error: err.message });
|
|
118
|
+
throw classifyWebrtcError(err);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
return { subscribe };
|
|
122
|
+
};
|
|
19
123
|
//#endregion
|
|
20
|
-
export {
|
|
124
|
+
export { createRealTimeSubscribeClient };
|
package/dist/realtime/types.d.ts
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
//#region src/realtime/types.d.ts
|
|
2
2
|
|
|
3
|
+
type GenerationEndedMessage = GenerationEnded & {
|
|
4
|
+
type: "generation_ended";
|
|
5
|
+
};
|
|
6
|
+
type QueuePositionMessage = {
|
|
7
|
+
type: "queue_position";
|
|
8
|
+
position: number;
|
|
9
|
+
queue_size: number;
|
|
10
|
+
};
|
|
11
|
+
type QueuePosition = {
|
|
12
|
+
position: number;
|
|
13
|
+
queueSize: number;
|
|
14
|
+
};
|
|
3
15
|
type ConnectionState = "connecting" | "connected" | "generating" | "disconnected" | "reconnecting";
|
|
16
|
+
type GenerationTick = {
|
|
17
|
+
seconds: number;
|
|
18
|
+
};
|
|
19
|
+
type GenerationEnded = {
|
|
20
|
+
seconds: number;
|
|
21
|
+
reason: string;
|
|
22
|
+
};
|
|
23
|
+
type ImageSetOptions = {
|
|
24
|
+
prompt?: string | null;
|
|
25
|
+
enhance?: boolean;
|
|
26
|
+
timeout?: number;
|
|
27
|
+
};
|
|
4
28
|
//#endregion
|
|
5
|
-
export { ConnectionState };
|
|
29
|
+
export { ConnectionState, GenerationEnded, GenerationEndedMessage, GenerationTick, ImageSetOptions, QueuePosition, QueuePositionMessage };
|
package/dist/shared/model.d.ts
CHANGED
|
@@ -318,11 +318,17 @@ declare const modelInputSchemas: {
|
|
|
318
318
|
}, z.core.$strip>;
|
|
319
319
|
};
|
|
320
320
|
type ModelInputSchemas = typeof modelInputSchemas;
|
|
321
|
+
type ModelFps = number | {
|
|
322
|
+
max?: number;
|
|
323
|
+
min?: number;
|
|
324
|
+
ideal?: number;
|
|
325
|
+
exact?: number;
|
|
326
|
+
};
|
|
321
327
|
type ModelDefinition<T extends Model = Model> = {
|
|
322
328
|
name: T;
|
|
323
329
|
urlPath: string;
|
|
324
330
|
queueUrlPath?: string;
|
|
325
|
-
fps:
|
|
331
|
+
fps: ModelFps;
|
|
326
332
|
width: number;
|
|
327
333
|
height: number;
|
|
328
334
|
inputSchema: T extends keyof ModelInputSchemas ? ModelInputSchemas[T] : z.ZodTypeAny;
|
|
@@ -341,6 +347,7 @@ type CustomModelDefinition = Omit<ModelDefinition, "name" | "inputSchema"> & {
|
|
|
341
347
|
* Requires `queueUrlPath` to distinguish from realtime definitions of the same model name.
|
|
342
348
|
*/
|
|
343
349
|
type ImageModelDefinition = ModelDefinition<ImageModels> & {
|
|
350
|
+
fps: number;
|
|
344
351
|
queueUrlPath: string;
|
|
345
352
|
};
|
|
346
353
|
/**
|
|
@@ -349,6 +356,7 @@ type ImageModelDefinition = ModelDefinition<ImageModels> & {
|
|
|
349
356
|
* Requires `queueUrlPath` to distinguish from realtime definitions of the same model name.
|
|
350
357
|
*/
|
|
351
358
|
type VideoModelDefinition = ModelDefinition<VideoModels> & {
|
|
359
|
+
fps: number;
|
|
352
360
|
queueUrlPath: string;
|
|
353
361
|
};
|
|
354
362
|
type ModelKind = "realtime" | "video" | "image";
|
|
@@ -386,6 +394,7 @@ declare const models: {
|
|
|
386
394
|
* - `"lucy-restyle-2"` - Video restyling
|
|
387
395
|
*/
|
|
388
396
|
video: <T extends VideoModels>(model: T) => ModelDefinition<T> & {
|
|
397
|
+
fps: number;
|
|
389
398
|
queueUrlPath: string;
|
|
390
399
|
};
|
|
391
400
|
/**
|
|
@@ -395,6 +404,7 @@ declare const models: {
|
|
|
395
404
|
* - `"lucy-image-2"` - Image-to-image editing
|
|
396
405
|
*/
|
|
397
406
|
image: <T extends ImageModels>(model: T) => ModelDefinition<T> & {
|
|
407
|
+
fps: number;
|
|
398
408
|
queueUrlPath: string;
|
|
399
409
|
};
|
|
400
410
|
};
|
package/dist/shared/model.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { createModelNotFoundError } from "../utils/errors.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
|
|
4
3
|
//#region src/shared/model.ts
|
|
5
4
|
const CANONICAL_MODEL_NAMES = [
|
|
6
5
|
"lucy-2.1",
|
|
@@ -195,11 +194,20 @@ const modelInputSchemas = {
|
|
|
195
194
|
"lucy-pro-i2i": imageEditSchema,
|
|
196
195
|
"lucy-restyle-v2v": restyleSchema
|
|
197
196
|
};
|
|
197
|
+
function resolveFpsNumber(fps, fallback = 30) {
|
|
198
|
+
if (typeof fps === "number") return fps;
|
|
199
|
+
return fps.ideal ?? fps.max ?? fps.exact ?? fps.min ?? fallback;
|
|
200
|
+
}
|
|
198
201
|
const modelDefinitionSchema = z.object({
|
|
199
202
|
name: z.string(),
|
|
200
203
|
urlPath: z.string(),
|
|
201
204
|
queueUrlPath: z.string().optional(),
|
|
202
|
-
fps: z.number().min(1),
|
|
205
|
+
fps: z.union([z.number().min(1), z.object({
|
|
206
|
+
max: z.number().min(1).optional(),
|
|
207
|
+
min: z.number().min(1).optional(),
|
|
208
|
+
ideal: z.number().min(1).optional(),
|
|
209
|
+
exact: z.number().min(1).optional()
|
|
210
|
+
})]),
|
|
203
211
|
width: z.number().min(1),
|
|
204
212
|
height: z.number().min(1),
|
|
205
213
|
inputSchema: z.any().optional()
|
|
@@ -209,7 +217,10 @@ const _models = {
|
|
|
209
217
|
"lucy-2.1": {
|
|
210
218
|
urlPath: "/v1/stream",
|
|
211
219
|
name: "lucy-2.1",
|
|
212
|
-
fps:
|
|
220
|
+
fps: {
|
|
221
|
+
ideal: 30,
|
|
222
|
+
max: 30
|
|
223
|
+
},
|
|
213
224
|
width: 1088,
|
|
214
225
|
height: 624,
|
|
215
226
|
inputSchema: z.object({})
|
|
@@ -217,7 +228,10 @@ const _models = {
|
|
|
217
228
|
"lucy-2.1-vton": {
|
|
218
229
|
urlPath: "/v1/stream",
|
|
219
230
|
name: "lucy-2.1-vton",
|
|
220
|
-
fps:
|
|
231
|
+
fps: {
|
|
232
|
+
ideal: 30,
|
|
233
|
+
max: 30
|
|
234
|
+
},
|
|
221
235
|
width: 1088,
|
|
222
236
|
height: 624,
|
|
223
237
|
inputSchema: z.object({})
|
|
@@ -225,7 +239,10 @@ const _models = {
|
|
|
225
239
|
"lucy-vton-2": {
|
|
226
240
|
urlPath: "/v1/stream",
|
|
227
241
|
name: "lucy-vton-2",
|
|
228
|
-
fps:
|
|
242
|
+
fps: {
|
|
243
|
+
ideal: 30,
|
|
244
|
+
max: 30
|
|
245
|
+
},
|
|
229
246
|
width: 1088,
|
|
230
247
|
height: 624,
|
|
231
248
|
inputSchema: z.object({})
|
|
@@ -233,7 +250,10 @@ const _models = {
|
|
|
233
250
|
"lucy-restyle-2": {
|
|
234
251
|
urlPath: "/v1/stream",
|
|
235
252
|
name: "lucy-restyle-2",
|
|
236
|
-
fps:
|
|
253
|
+
fps: {
|
|
254
|
+
ideal: 30,
|
|
255
|
+
max: 30
|
|
256
|
+
},
|
|
237
257
|
width: 1280,
|
|
238
258
|
height: 704,
|
|
239
259
|
inputSchema: z.object({})
|
|
@@ -241,7 +261,10 @@ const _models = {
|
|
|
241
261
|
"lucy-latest": {
|
|
242
262
|
urlPath: "/v1/stream",
|
|
243
263
|
name: "lucy-latest",
|
|
244
|
-
fps:
|
|
264
|
+
fps: {
|
|
265
|
+
ideal: 30,
|
|
266
|
+
max: 30
|
|
267
|
+
},
|
|
245
268
|
width: 1088,
|
|
246
269
|
height: 624,
|
|
247
270
|
inputSchema: z.object({})
|
|
@@ -249,7 +272,10 @@ const _models = {
|
|
|
249
272
|
"lucy-vton-latest": {
|
|
250
273
|
urlPath: "/v1/stream",
|
|
251
274
|
name: "lucy-vton-latest",
|
|
252
|
-
fps:
|
|
275
|
+
fps: {
|
|
276
|
+
ideal: 30,
|
|
277
|
+
max: 30
|
|
278
|
+
},
|
|
253
279
|
width: 1088,
|
|
254
280
|
height: 624,
|
|
255
281
|
inputSchema: z.object({})
|
|
@@ -257,7 +283,10 @@ const _models = {
|
|
|
257
283
|
"lucy-restyle-latest": {
|
|
258
284
|
urlPath: "/v1/stream",
|
|
259
285
|
name: "lucy-restyle-latest",
|
|
260
|
-
fps:
|
|
286
|
+
fps: {
|
|
287
|
+
ideal: 30,
|
|
288
|
+
max: 30
|
|
289
|
+
},
|
|
261
290
|
width: 1280,
|
|
262
291
|
height: 704,
|
|
263
292
|
inputSchema: z.object({})
|
|
@@ -265,7 +294,10 @@ const _models = {
|
|
|
265
294
|
mirage_v2: {
|
|
266
295
|
urlPath: "/v1/stream",
|
|
267
296
|
name: "mirage_v2",
|
|
268
|
-
fps:
|
|
297
|
+
fps: {
|
|
298
|
+
ideal: 30,
|
|
299
|
+
max: 30
|
|
300
|
+
},
|
|
269
301
|
width: 1280,
|
|
270
302
|
height: 704,
|
|
271
303
|
inputSchema: z.object({})
|
|
@@ -273,7 +305,10 @@ const _models = {
|
|
|
273
305
|
"lucy-vton": {
|
|
274
306
|
urlPath: "/v1/stream",
|
|
275
307
|
name: "lucy-vton",
|
|
276
|
-
fps:
|
|
308
|
+
fps: {
|
|
309
|
+
ideal: 30,
|
|
310
|
+
max: 30
|
|
311
|
+
},
|
|
277
312
|
width: 1088,
|
|
278
313
|
height: 624,
|
|
279
314
|
inputSchema: z.object({})
|
|
@@ -281,7 +316,10 @@ const _models = {
|
|
|
281
316
|
"lucy-2.1-vton-2": {
|
|
282
317
|
urlPath: "/v1/stream",
|
|
283
318
|
name: "lucy-2.1-vton-2",
|
|
284
|
-
fps:
|
|
319
|
+
fps: {
|
|
320
|
+
ideal: 30,
|
|
321
|
+
max: 30
|
|
322
|
+
},
|
|
285
323
|
width: 1088,
|
|
286
324
|
height: 624,
|
|
287
325
|
inputSchema: z.object({})
|
|
@@ -479,6 +517,5 @@ const models = {
|
|
|
479
517
|
return modelDefinition;
|
|
480
518
|
}
|
|
481
519
|
};
|
|
482
|
-
|
|
483
520
|
//#endregion
|
|
484
|
-
export { isCanonicalModel, isImageModel, isModel, isRealtimeModel, isVideoModel, listModels, modelAliases, modelDefinitionSchema, models, resolveCanonicalModelAlias, resolveModelAlias };
|
|
521
|
+
export { isCanonicalModel, isImageModel, isModel, isRealtimeModel, isVideoModel, listModels, modelAliases, modelDefinitionSchema, models, resolveCanonicalModelAlias, resolveFpsNumber, resolveModelAlias };
|
package/dist/shared/request.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { createInvalidInputError } from "../utils/errors.js";
|
|
2
2
|
import { buildUserAgent } from "../utils/user-agent.js";
|
|
3
|
-
|
|
4
3
|
//#region src/shared/request.ts
|
|
5
4
|
/**
|
|
6
5
|
* Type guard to check if a value is a React Native file object.
|
|
@@ -46,6 +45,5 @@ function buildFormData(inputs) {
|
|
|
46
45
|
else formData.append(key, String(value));
|
|
47
46
|
return formData;
|
|
48
47
|
}
|
|
49
|
-
|
|
50
48
|
//#endregion
|
|
51
|
-
export { buildAuthHeaders, buildFormData, fileInputToBlob };
|
|
49
|
+
export { buildAuthHeaders, buildFormData, fileInputToBlob };
|
package/dist/shared/types.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
|
|
3
2
|
//#region src/shared/types.ts
|
|
4
3
|
const modelStateSchema = z.object({
|
|
5
4
|
prompt: z.object({
|
|
@@ -12,6 +11,5 @@ const modelStateSchema = z.object({
|
|
|
12
11
|
z.string()
|
|
13
12
|
]).optional()
|
|
14
13
|
});
|
|
15
|
-
|
|
16
14
|
//#endregion
|
|
17
|
-
export { modelStateSchema };
|
|
15
|
+
export { modelStateSchema };
|
package/dist/tokens/client.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { createSDKError } from "../utils/errors.js";
|
|
2
2
|
import { buildAuthHeaders } from "../shared/request.js";
|
|
3
|
-
|
|
4
3
|
//#region src/tokens/client.ts
|
|
5
4
|
const createTokensClient = (opts) => {
|
|
6
5
|
const { baseUrl, apiKey, integration } = opts;
|
|
@@ -25,6 +24,5 @@ const createTokensClient = (opts) => {
|
|
|
25
24
|
};
|
|
26
25
|
return { create };
|
|
27
26
|
};
|
|
28
|
-
|
|
29
27
|
//#endregion
|
|
30
|
-
export { createTokensClient };
|
|
28
|
+
export { createTokensClient };
|