@decartai/sdk 0.1.12 → 0.1.14

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 (42) hide show
  1. package/README.md +85 -1
  2. package/dist/create-client.d.ts +46 -0
  3. package/dist/create-client.js +106 -0
  4. package/dist/index.d.ts +8 -150
  5. package/dist/index.js +6 -116
  6. package/dist/index.react-native.d.ts +36 -0
  7. package/dist/index.react-native.js +9 -0
  8. package/dist/realtime/browser/glass-to-glass-diagnostics.js +27 -0
  9. package/dist/realtime/{observability → browser}/glass-to-glass.js +2 -19
  10. package/dist/realtime/browser/index.js +32 -0
  11. package/dist/realtime/{mirror-stream.js → browser/mirror-stream.js} +1 -1
  12. package/dist/realtime/{observability → browser}/pixel-marker.js +1 -1
  13. package/dist/realtime/{preflight.js → browser/preflight.js} +14 -108
  14. package/dist/realtime/browser/prepare-connection.js +39 -0
  15. package/dist/realtime/client.d.ts +11 -4
  16. package/dist/realtime/client.js +33 -37
  17. package/dist/realtime/livekit.js +26 -0
  18. package/dist/realtime/media-channel.js +17 -10
  19. package/dist/realtime/methods.d.ts +1 -1
  20. package/dist/realtime/methods.js +3 -2
  21. package/dist/realtime/observability/g2g.d.ts +14 -0
  22. package/dist/realtime/observability/realtime-observability.js +8 -18
  23. package/dist/realtime/observability/webrtc-stats.d.ts +1 -1
  24. package/dist/realtime/preflight-connectivity.js +102 -0
  25. package/dist/realtime/preflight-types.d.ts +30 -0
  26. package/dist/realtime/react-native/index.js +36 -0
  27. package/dist/realtime/react-native/preflight.js +10 -0
  28. package/dist/realtime/react-native/prepare-connection.js +26 -0
  29. package/dist/realtime/stream-session.js +1 -2
  30. package/dist/realtime/subscribe-client.js +10 -9
  31. package/dist/shared/model.d.ts +36 -36
  32. package/dist/shared/model.js +13 -22
  33. package/dist/shared/request.js +5 -4
  34. package/dist/shared/types.d.ts +1 -1
  35. package/dist/shared/types.js +3 -2
  36. package/dist/utils/errors.d.ts +3 -0
  37. package/dist/utils/errors.js +16 -1
  38. package/dist/utils/platform.js +17 -1
  39. package/dist/utils/runtime.js +13 -0
  40. package/package.json +10 -3
  41. package/dist/realtime/observability/glass-to-glass.d.ts +0 -41
  42. package/dist/realtime/preflight.d.ts +0 -59
@@ -1,4 +1,5 @@
1
1
  import { createModelNotFoundError } from "../utils/errors.js";
2
+ import { globalInstanceSchema } from "../utils/runtime.js";
2
3
  import { z } from "zod";
3
4
  //#region src/shared/model.ts
4
5
  const CANONICAL_MODEL_NAMES = [
@@ -35,7 +36,6 @@ const canonicalModelSchema = z.enum(CANONICAL_MODEL_NAMES);
35
36
  * Old names still work but will log a deprecation warning.
36
37
  */
37
38
  const modelAliases = {
38
- mirage_v2: "lucy-restyle-2",
39
39
  "lucy-2.1-vton-2": "lucy-vton-2",
40
40
  "lucy-pro-v2v": "lucy-clip",
41
41
  "lucy-restyle-v2v": "lucy-restyle-2",
@@ -58,7 +58,6 @@ const realtimeModels = z.union([
58
58
  z.literal("lucy-latest"),
59
59
  z.literal("lucy-vton-latest"),
60
60
  z.literal("lucy-restyle-latest"),
61
- z.literal("mirage_v2"),
62
61
  z.literal("lucy-2.1-vton-2")
63
62
  ]);
64
63
  const videoModels = z.union([
@@ -124,10 +123,10 @@ function resolveCanonicalModelAlias(model) {
124
123
  return parsedModel.success ? parsedModel.data : void 0;
125
124
  }
126
125
  const fileInputSchema = z.union([
127
- z.instanceof(File),
128
- z.instanceof(Blob),
129
- z.instanceof(ReadableStream),
130
- z.instanceof(URL),
126
+ globalInstanceSchema("File"),
127
+ globalInstanceSchema("Blob"),
128
+ globalInstanceSchema("ReadableStream"),
129
+ globalInstanceSchema("URL"),
131
130
  z.url(),
132
131
  z.object({
133
132
  uri: z.string(),
@@ -230,9 +229,12 @@ const _models = {
230
229
  "lucy-2.5": {
231
230
  urlPath: "/v1/stream",
232
231
  name: "lucy-2.5",
233
- fps: 20,
234
- width: 1088,
235
- height: 624,
232
+ fps: {
233
+ ideal: 30,
234
+ max: 30
235
+ },
236
+ width: 1280,
237
+ height: 720,
236
238
  inputSchema: z.object({})
237
239
  },
238
240
  "lucy-vton-2": {
@@ -301,17 +303,6 @@ const _models = {
301
303
  height: 704,
302
304
  inputSchema: z.object({})
303
305
  },
304
- mirage_v2: {
305
- urlPath: "/v1/stream",
306
- name: "mirage_v2",
307
- fps: {
308
- ideal: 30,
309
- max: 30
310
- },
311
- width: 1280,
312
- height: 704,
313
- inputSchema: z.object({})
314
- },
315
306
  "lucy-2.1-vton-2": {
316
307
  urlPath: "/v1/stream",
317
308
  name: "lucy-2.1-vton-2",
@@ -377,8 +368,8 @@ const _models = {
377
368
  queueUrlPath: "/v1/jobs/lucy-2.5",
378
369
  name: "lucy-2.5",
379
370
  fps: 20,
380
- width: 1088,
381
- height: 624,
371
+ width: 1280,
372
+ height: 720,
382
373
  inputSchema: modelInputSchemas["lucy-2.5"]
383
374
  },
384
375
  "lucy-vton-2": {
@@ -1,4 +1,5 @@
1
1
  import { createInvalidInputError } from "../utils/errors.js";
2
+ import { isGlobalInstance } from "../utils/runtime.js";
2
3
  import { buildUserAgent } from "../utils/user-agent.js";
3
4
  //#region src/shared/request.ts
4
5
  /**
@@ -13,9 +14,9 @@ function isReactNativeFile(value) {
13
14
  */
14
15
  async function fileInputToBlob(input) {
15
16
  if (isReactNativeFile(input)) return input;
16
- if (input instanceof Blob || input instanceof File) return input;
17
- if (input instanceof ReadableStream) return new Response(input).blob();
18
- if (typeof input === "string" || input instanceof URL) {
17
+ if (isGlobalInstance(input, "Blob") || isGlobalInstance(input, "File")) return input;
18
+ if (isGlobalInstance(input, "ReadableStream")) return new Response(input).blob();
19
+ if (typeof input === "string" || isGlobalInstance(input, "URL")) {
19
20
  const url = typeof input === "string" ? input : input.toString();
20
21
  if (!url.startsWith("http://") && !url.startsWith("https://")) throw createInvalidInputError("URL must start with http:// or https://");
21
22
  const response = await fetch(url);
@@ -40,7 +41,7 @@ function buildAuthHeaders(options = {}) {
40
41
  function buildFormData(inputs) {
41
42
  const formData = new FormData();
42
43
  for (const [key, value] of Object.entries(inputs)) if (value !== void 0 && value !== null) if (isReactNativeFile(value)) formData.append(key, value);
43
- else if (value instanceof Blob) formData.append(key, value);
44
+ else if (isGlobalInstance(value, "Blob")) formData.append(key, value);
44
45
  else if (typeof value === "object" && value !== null) formData.append(key, JSON.stringify(value));
45
46
  else formData.append(key, String(value));
46
47
  return formData;
@@ -6,7 +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
+ image: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<Blob, unknown, z.core.$ZodTypeInternals<Blob, unknown>>, z.ZodType<File, unknown, z.core.$ZodTypeInternals<File, unknown>>, z.ZodString]>>;
10
10
  passthrough: z.ZodOptional<z.ZodBoolean>;
11
11
  }, z.core.$strip>;
12
12
  type ModelState = z.infer<typeof modelStateSchema>;
@@ -1,3 +1,4 @@
1
+ import { globalInstanceSchema } from "../utils/runtime.js";
1
2
  import { z } from "zod";
2
3
  //#region src/shared/types.ts
3
4
  const modelStateSchema = z.object({
@@ -6,8 +7,8 @@ const modelStateSchema = z.object({
6
7
  enhance: z.boolean().optional().default(true)
7
8
  }).optional(),
8
9
  image: z.union([
9
- z.instanceof(Blob),
10
- z.instanceof(File),
10
+ globalInstanceSchema("Blob"),
11
+ globalInstanceSchema("File"),
11
12
  z.string()
12
13
  ]).optional(),
13
14
  passthrough: z.boolean().optional()
@@ -20,6 +20,9 @@ declare const ERROR_CODES: {
20
20
  readonly FILES_UPLOAD_ERROR: "FILES_UPLOAD_ERROR";
21
21
  readonly FILES_GET_ERROR: "FILES_GET_ERROR";
22
22
  readonly FILES_DELETE_ERROR: "FILES_DELETE_ERROR";
23
+ readonly REACT_NATIVE_SETUP_REQUIRED: "REACT_NATIVE_SETUP_REQUIRED";
24
+ readonly UNSUPPORTED_PLATFORM_FEATURE: "UNSUPPORTED_PLATFORM_FEATURE";
25
+ readonly LIVEKIT_INITIALIZATION_ERROR: "LIVEKIT_INITIALIZATION_ERROR";
23
26
  readonly WEBRTC_WEBSOCKET_ERROR: "WEBRTC_WEBSOCKET_ERROR";
24
27
  readonly WEBRTC_ICE_ERROR: "WEBRTC_ICE_ERROR";
25
28
  readonly WEBRTC_TIMEOUT_ERROR: "WEBRTC_TIMEOUT_ERROR";
@@ -14,6 +14,9 @@ const ERROR_CODES = {
14
14
  FILES_UPLOAD_ERROR: "FILES_UPLOAD_ERROR",
15
15
  FILES_GET_ERROR: "FILES_GET_ERROR",
16
16
  FILES_DELETE_ERROR: "FILES_DELETE_ERROR",
17
+ REACT_NATIVE_SETUP_REQUIRED: "REACT_NATIVE_SETUP_REQUIRED",
18
+ UNSUPPORTED_PLATFORM_FEATURE: "UNSUPPORTED_PLATFORM_FEATURE",
19
+ LIVEKIT_INITIALIZATION_ERROR: "LIVEKIT_INITIALIZATION_ERROR",
17
20
  WEBRTC_WEBSOCKET_ERROR: "WEBRTC_WEBSOCKET_ERROR",
18
21
  WEBRTC_ICE_ERROR: "WEBRTC_ICE_ERROR",
19
22
  WEBRTC_TIMEOUT_ERROR: "WEBRTC_TIMEOUT_ERROR",
@@ -34,6 +37,18 @@ function createInvalidApiKeyError() {
34
37
  function createInvalidBaseUrlError(url) {
35
38
  return createSDKError(ERROR_CODES.INVALID_BASE_URL, `Invalid base URL${url ? `: ${url}` : ""}`);
36
39
  }
40
+ function createReactNativeSetupRequiredError(missing) {
41
+ return createSDKError(ERROR_CODES.REACT_NATIVE_SETUP_REQUIRED, "React Native realtime requires @livekit/react-native registerGlobals() before calling Decart realtime APIs.", { missing });
42
+ }
43
+ function createUnsupportedPlatformFeatureError(feature, platform) {
44
+ return createSDKError(ERROR_CODES.UNSUPPORTED_PLATFORM_FEATURE, `${feature} is not supported on ${platform}.`, {
45
+ feature,
46
+ platform
47
+ });
48
+ }
49
+ function createLiveKitInitializationError(message, cause) {
50
+ return createSDKError(ERROR_CODES.LIVEKIT_INITIALIZATION_ERROR, message, void 0, cause);
51
+ }
37
52
  function createWebrtcWebsocketError(error) {
38
53
  return createSDKError(ERROR_CODES.WEBRTC_WEBSOCKET_ERROR, "WebSocket connection failed", void 0, error);
39
54
  }
@@ -83,4 +98,4 @@ function createQueueResultError(message, status) {
83
98
  return createSDKError(ERROR_CODES.QUEUE_RESULT_ERROR, message, { status });
84
99
  }
85
100
  //#endregion
86
- export { ERROR_CODES, classifyWebrtcError, createInvalidApiKeyError, createInvalidBaseUrlError, createInvalidInputError, createModelNotFoundError, createQueueResultError, createQueueStatusError, createQueueSubmitError, createSDKError };
101
+ export { ERROR_CODES, classifyWebrtcError, createInvalidApiKeyError, createInvalidBaseUrlError, createInvalidInputError, createLiveKitInitializationError, createModelNotFoundError, createQueueResultError, createQueueStatusError, createQueueSubmitError, createReactNativeSetupRequiredError, createSDKError, createUnsupportedPlatformFeatureError };
@@ -1,4 +1,20 @@
1
1
  //#region src/utils/platform.ts
2
+ const REACT_NATIVE_REALTIME_GLOBALS = [
3
+ "LiveKitReactNativeGlobal",
4
+ "RTCPeerConnection",
5
+ "MediaStream",
6
+ "MediaStreamTrack",
7
+ "DOMException",
8
+ "ReadableStream",
9
+ "WritableStream",
10
+ "TransformStream",
11
+ "URL",
12
+ "URLSearchParams"
13
+ ];
14
+ function missingReactNativeRealtimeGlobals() {
15
+ const g = globalThis;
16
+ return REACT_NATIVE_REALTIME_GLOBALS.filter((name) => g[name] === void 0);
17
+ }
2
18
  function isDesktopSafari() {
3
19
  const g = globalThis;
4
20
  const ua = g?.navigator?.userAgent ?? "";
@@ -10,4 +26,4 @@ function isDesktopSafari() {
10
26
  return true;
11
27
  }
12
28
  //#endregion
13
- export { isDesktopSafari };
29
+ export { isDesktopSafari, missingReactNativeRealtimeGlobals };
@@ -0,0 +1,13 @@
1
+ import { z } from "zod";
2
+ //#region src/utils/runtime.ts
3
+ /** Safely test a platform-provided constructor without evaluating a missing global. */
4
+ function isGlobalInstance(value, name) {
5
+ const runtimeConstructor = globalThis[name];
6
+ return typeof runtimeConstructor === "function" && value instanceof runtimeConstructor;
7
+ }
8
+ /** Zod schema for values whose constructor may not exist in every runtime. */
9
+ function globalInstanceSchema(name) {
10
+ return z.custom((value) => isGlobalInstance(value, name), { message: `Expected ${name}` });
11
+ }
12
+ //#endregion
13
+ export { globalInstanceSchema, isGlobalInstance };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decartai/sdk",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Decart's JavaScript SDK",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -20,9 +20,15 @@
20
20
  ],
21
21
  "main": "./dist/index.js",
22
22
  "module": "./dist/index.js",
23
+ "react-native": "./dist/index.react-native.js",
23
24
  "types": "./dist/index.d.ts",
24
25
  "exports": {
25
- ".": "./dist/index.js",
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "react-native": "./dist/index.react-native.js",
29
+ "browser": "./dist/index.js",
30
+ "default": "./dist/index.js"
31
+ },
26
32
  "./package.json": "./package.json"
27
33
  },
28
34
  "publishConfig": {
@@ -43,7 +49,7 @@
43
49
  "vitest": "^4.0.18"
44
50
  },
45
51
  "dependencies": {
46
- "livekit-client": "^2.0.0",
52
+ "livekit-client": "~2.20.1",
47
53
  "mitt": "^3.0.1",
48
54
  "p-retry": "^6.2.1",
49
55
  "zod": "^4.0.17"
@@ -53,6 +59,7 @@
53
59
  "dev": "tsdown --watch",
54
60
  "dev:example": "vite dev --port 3000",
55
61
  "test": "vitest unit",
62
+ "test:package": "node tests/built-package.mjs",
56
63
  "test:e2e": "vitest e2e",
57
64
  "test:e2e:realtime": "vitest --config vitest.config.e2e-realtime.ts",
58
65
  "typecheck": "tsc --noEmit",
@@ -1,41 +0,0 @@
1
- //#region src/realtime/observability/glass-to-glass.d.ts
2
-
3
- /**
4
- * Aggregated glass-to-glass metrics. TTFF (startup) and mid-stream (steady
5
- * state) are measured separately — they differ by an order of magnitude and the
6
- * cold-start frames must not pollute the steady-state numbers.
7
- */
8
- type G2GMetrics = {
9
- /**
10
- * Time-to-first-frame (ms): from the connect attempt start to the first
11
- * rendered model output. A one-shot startup metric, ~seconds. Null until the
12
- * first frame arrives.
13
- */
14
- ttffMs: number | null;
15
- /**
16
- * Median mid-stream (steady-state) glass-to-glass latency (ms), excluding the
17
- * warm-up after the first frame. Null until past warm-up. This is the
18
- * per-frame responsiveness, distinct from `ttffMs`.
19
- */
20
- medianMs: number | null;
21
- /** p90 mid-stream glass-to-glass latency (ms), or null until past warm-up. */
22
- p90Ms: number | null;
23
- /** Mid-stream latency samples in the window (post-warm-up). */
24
- sampleCount: number;
25
- /**
26
- * End-to-end frame drop ratio (0–1): seqs stamped but never rendered, over
27
- * recent post-warm-up outcomes. Null until enough frames have completed the
28
- * round trip — short sessions/probes may never produce it.
29
- */
30
- dropRatio: number | null;
31
- };
32
- /**
33
- * Matches outgoing stamp times to incoming render times. Shared by the stamp
34
- * pump (writer) and the marker reader (matcher); owned by `RealtimeObservability`.
35
- *
36
- * Tracks two latencies: TTFF (start → first frame) and mid-stream median (steady
37
- * state, after a warm-up). Call `markStart()` at the beginning of each connect
38
- * attempt so TTFF measures the full setup→first-frame wait.
39
- */
40
- //#endregion
41
- export { G2GMetrics };
@@ -1,59 +0,0 @@
1
- import { CustomModelDefinition, ModelDefinition } from "../shared/model.js";
2
- import { ConnectionQuality } from "./observability/connection-quality.js";
3
-
4
- //#region src/realtime/preflight.d.ts
5
-
6
- /**
7
- * SDK-only connectivity preflight — run before `realtime.connect()` to decide
8
- * whether to show the integration. Spins up a throwaway `RTCPeerConnection`
9
- * against public STUN (no session, no inference) to check whether WebRTC can
10
- * leave the network over UDP and roughly how laggy the path is. It does not
11
- * measure throughput — use the in-session `connectionQuality` signal for that.
12
- */
13
- type ConnectivityTransport = "udp" | "relay" | "failed";
14
- type ConnectivityMetrics = {
15
- /** "udp" = direct UDP works · "relay" = will need TURN (unverified SDK-only) · "failed" = no connectivity. */
16
- transport: ConnectivityTransport;
17
- /** Approximate network round-trip time (ms) from time-to-first STUN candidate (or real RTT in deep mode), or null. */
18
- rttMs: number | null;
19
- /** Active-probe only: measured mid-stream (steady-state) glass-to-glass latency (ms), or null. */
20
- g2gMs?: number | null;
21
- /** Active-probe only: time-to-first-frame (ms) — startup latency to the first rendered model frame, or null. */
22
- ttffMs?: number | null;
23
- /** Active-probe only: end-to-end frame drop ratio (0–1), or null. */
24
- g2gDropRatio?: number | null;
25
- /** Active-probe only: server's view of upstream jitter (ms), or null. */
26
- upstreamJitterMs?: number | null;
27
- /** Active-probe only: server-reported upstream packet loss (0–1), or null. */
28
- packetLoss?: number | null;
29
- /** Active-probe only: number of glass-to-glass samples collected. */
30
- sampleCount?: number;
31
- };
32
- type ConnectivityReport = {
33
- /** Pre-connect quality on the same `good → critical` scale as the in-session signal — you decide what to do. */
34
- quality: ConnectionQuality;
35
- metrics: ConnectivityMetrics;
36
- /** Human-readable explanations for any non-"good" verdict. */
37
- reasons: string[];
38
- };
39
- type CheckConnectivityOptions = {
40
- /** Override the ICE servers used for the probe. Defaults to public STUN. */
41
- iceServers?: RTCIceServer[];
42
- /** Abort candidate gathering after this long. Defaults to config. */
43
- iceGatherTimeoutMs?: number;
44
- /** Abort the probe early. */
45
- signal?: AbortSignal;
46
- /**
47
- * Opt-in "deep" probe: instead of the STUN-only network check, briefly open a
48
- * real session with a synthetic source, measure true glass-to-glass latency,
49
- * then tear it down. Requires `model`. Costs a short GPU session.
50
- */
51
- deep?: boolean;
52
- /** Required when `deep`: the realtime model to probe (latency is model-specific). */
53
- model?: ModelDefinition | CustomModelDefinition;
54
- /** Deep-probe duration (ms). Defaults to config. */
55
- durationMs?: number;
56
- };
57
- /** Realtime `connect` injected by the SDK root so the active probe can open a session. */
58
- //#endregion
59
- export { CheckConnectivityOptions, ConnectivityMetrics, ConnectivityReport, ConnectivityTransport };