@decartai/sdk 0.0.68 → 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.
- package/README.md +49 -9
- package/dist/index.d.ts +6 -4
- package/dist/index.js +43 -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 +17 -11
- package/dist/realtime/client.js +71 -155
- 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 +12 -42
- 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 +286 -0
- package/dist/realtime/stream-session.js +252 -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.js +1 -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
package/README.md
CHANGED
|
@@ -15,12 +15,15 @@ yarn add @decartai/sdk
|
|
|
15
15
|
## Documentation
|
|
16
16
|
|
|
17
17
|
For complete documentation, guides, and examples, visit:
|
|
18
|
-
**https://docs.platform.decart.ai/sdks/javascript**
|
|
18
|
+
**[https://docs.platform.decart.ai/sdks/javascript](https://docs.platform.decart.ai/sdks/javascript)**
|
|
19
19
|
|
|
20
20
|
## Quick Start
|
|
21
21
|
|
|
22
22
|
### Real-time Video Transformation
|
|
23
23
|
|
|
24
|
+
Realtime connections are LiveKit-backed in the SDK. Existing client usage stays the same: provide a
|
|
25
|
+
camera `MediaStream`, choose a realtime model, and handle the transformed remote stream.
|
|
26
|
+
|
|
24
27
|
```typescript
|
|
25
28
|
import { createDecartClient, models } from "@decartai/sdk";
|
|
26
29
|
|
|
@@ -79,16 +82,53 @@ Options:
|
|
|
79
82
|
- `"auto"` — mirror when the input track reports `facingMode: "user"` (mobile front cameras).
|
|
80
83
|
- `true` — always mirror (e.g. desktop webcams).
|
|
81
84
|
|
|
82
|
-
|
|
85
|
+
### Watch a Stream
|
|
83
86
|
|
|
84
|
-
|
|
87
|
+
A connected realtime session exposes an SDK `subscribeToken` once it reaches a
|
|
88
|
+
connected state. Share that SDK token with viewers — `client.realtime.subscribe`
|
|
89
|
+
uses it to request receive-only LiveKit credentials from Decart, then connects to
|
|
90
|
+
the LiveKit room for the styled output stream. No viewer camera is required.
|
|
91
|
+
|
|
92
|
+
**Producer** — capture the token from the active session:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
85
95
|
const realtimeClient = await client.realtime.connect(stream, {
|
|
86
96
|
model,
|
|
87
|
-
|
|
88
|
-
|
|
97
|
+
onRemoteStream: (transformedStream) => {
|
|
98
|
+
videoElement.srcObject = transformedStream;
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
realtimeClient.on("connectionChange", (state) => {
|
|
103
|
+
if ((state === "connected" || state === "generating") && realtimeClient.subscribeToken) {
|
|
104
|
+
const subscribeToken = realtimeClient.subscribeToken;
|
|
105
|
+
// Pass `subscribeToken` to the viewer snippet below.
|
|
106
|
+
}
|
|
89
107
|
});
|
|
90
108
|
```
|
|
91
109
|
|
|
110
|
+
**Viewer** — attach to the producer's stream with the token:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { createDecartClient, type RealTimeSubscribeClient } from "@decartai/sdk";
|
|
114
|
+
|
|
115
|
+
const client = createDecartClient({ apiKey: "your-api-key-here" });
|
|
116
|
+
|
|
117
|
+
const subscriber: RealTimeSubscribeClient = await client.realtime.subscribe({
|
|
118
|
+
token: subscribeToken,
|
|
119
|
+
onRemoteStream: (stream) => {
|
|
120
|
+
videoElement.srcObject = stream;
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
subscriber.on("connectionChange", (state) => {
|
|
125
|
+
console.log(`Viewer state: ${state}`);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Disconnect when done
|
|
129
|
+
subscriber.disconnect();
|
|
130
|
+
```
|
|
131
|
+
|
|
92
132
|
### Async Processing (Queue API)
|
|
93
133
|
|
|
94
134
|
For video generation jobs, use the queue API to submit jobs and poll for results:
|
|
@@ -161,12 +201,12 @@ pnpm install
|
|
|
161
201
|
|
|
162
202
|
1. **Version bump**: Run `pnpm release` to bump the version (this uses `bumpp` to create a new version tag) and push it to GitHub
|
|
163
203
|
2. **Automated publish**: The GitHub Actions workflow will:
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
204
|
+
- Build the project
|
|
205
|
+
- Publish to npm
|
|
206
|
+
- Create a GitHub release with changelog
|
|
167
207
|
|
|
168
208
|
The package is published to npm as `@decartai/sdk`.
|
|
169
209
|
|
|
170
210
|
## License
|
|
171
211
|
|
|
172
|
-
MIT
|
|
212
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -5,12 +5,12 @@ import { ProcessClient } from "./process/client.js";
|
|
|
5
5
|
import { JobStatus, JobStatusResponse, JobSubmitResponse, QueueJobResult, QueueSubmitAndPollOptions, QueueSubmitOptions } from "./queue/types.js";
|
|
6
6
|
import { QueueClient } from "./queue/client.js";
|
|
7
7
|
import { DecartSDKError, ERROR_CODES } from "./utils/errors.js";
|
|
8
|
-
import {
|
|
9
|
-
import { ConnectionPhase, DiagnosticEvent, DiagnosticEventName, DiagnosticEvents, IceCandidateEvent, IceStateEvent, PeerConnectionStateEvent, PhaseTimingEvent, ReconnectEvent, SelectedCandidatePairEvent, SignalingStateEvent, VideoStallEvent } from "./realtime/observability/diagnostics.js";
|
|
8
|
+
import { ClientSessionConnectionBreakdownEvent, ClientSessionConnectionBreakdownPhase, DiagnosticEvent, DiagnosticEventName, DiagnosticEvents, ReconnectEvent, VideoStallEvent } from "./realtime/observability/diagnostics.js";
|
|
10
9
|
import { WebRTCStats } from "./realtime/observability/webrtc-stats.js";
|
|
10
|
+
import { ConnectionState, GenerationEndedMessage, QueuePosition, QueuePositionMessage } from "./realtime/types.js";
|
|
11
11
|
import { SetInput } from "./realtime/methods.js";
|
|
12
|
-
import { RealTimeSubscribeClient, SubscribeEvents, SubscribeOptions } from "./realtime/subscribe-client.js";
|
|
13
12
|
import { Events, RealTimeClient, RealTimeClientConnectOptions, RealTimeClientInitialState } from "./realtime/client.js";
|
|
13
|
+
import { RealTimeSubscribeClient, SubscribeEvents, SubscribeOptions } from "./realtime/subscribe-client.js";
|
|
14
14
|
import { ModelState } from "./shared/types.js";
|
|
15
15
|
import { CreateTokenOptions, CreateTokenResponse, TokensClient } from "./tokens/client.js";
|
|
16
16
|
|
|
@@ -42,6 +42,8 @@ type DecartClientOptions = {
|
|
|
42
42
|
* @param options.realtimeBaseUrl - Override the default WebSocket base URL for realtime connections.
|
|
43
43
|
* @param options.integration - Optional integration identifier.
|
|
44
44
|
*
|
|
45
|
+
* Realtime media uses LiveKit only (inference must enable it in `TRANSPORTS_ENABLED`).
|
|
46
|
+
*
|
|
45
47
|
* @example
|
|
46
48
|
* ```ts
|
|
47
49
|
* // (direct API access)Option 1: Explicit API key
|
|
@@ -134,4 +136,4 @@ declare const createDecartClient: (options?: DecartClientOptions) => {
|
|
|
134
136
|
tokens: TokensClient;
|
|
135
137
|
};
|
|
136
138
|
//#endregion
|
|
137
|
-
export { type CanonicalModel, type
|
|
139
|
+
export { type CanonicalModel, type ClientSessionConnectionBreakdownEvent, type ClientSessionConnectionBreakdownPhase, type ConnectionState, type CreateTokenOptions, type CreateTokenResponse, type CustomModelDefinition, DecartClientOptions, type DecartSDKError, type DiagnosticEvent, type DiagnosticEventName, type DiagnosticEvents, ERROR_CODES, type FileInput, type GenerationEndedMessage, type ImageModelDefinition, type ImageModels, type JobStatus, type JobStatusResponse, type JobSubmitResponse, type ListedModelDefinition, type LogLevel, type Logger, type Model, type ModelDefinition, type ModelKind, type ModelState, type ProcessClient, type ProcessOptions, type QueueClient, type QueueJobResult, type QueuePosition, type QueuePositionMessage, type QueueSubmitAndPollOptions, type QueueSubmitOptions, type ReactNativeFile, type RealTimeClient, type RealTimeClientConnectOptions, type RealTimeClientInitialState, type Events as RealTimeEvents, type RealTimeModels, type RealTimeSubscribeClient, type ReconnectEvent, type SetInput, type SubscribeEvents, type SubscribeOptions, type TokensClient, type VideoModelDefinition, type VideoModels, type VideoStallEvent, type WebRTCStats, createConsoleLogger, createDecartClient, isCanonicalModel, isImageModel, isModel, isRealtimeModel, isVideoModel, listModels, modelAliases, models, noopLogger, resolveCanonicalModelAlias, resolveModelAlias };
|
package/dist/index.js
CHANGED
|
@@ -2,12 +2,12 @@ import { ERROR_CODES, createInvalidApiKeyError, createInvalidBaseUrlError } from
|
|
|
2
2
|
import { createProcessClient } from "./process/client.js";
|
|
3
3
|
import { createQueueClient } from "./queue/client.js";
|
|
4
4
|
import { isCanonicalModel, isImageModel, isModel, isRealtimeModel, isVideoModel, listModels, modelAliases, models, resolveCanonicalModelAlias, resolveModelAlias } from "./shared/model.js";
|
|
5
|
+
import { createConsoleLogger, noopLogger } from "./utils/logger.js";
|
|
5
6
|
import { createRealTimeClient } from "./realtime/client.js";
|
|
7
|
+
import { createRealTimeSubscribeClient } from "./realtime/subscribe-client.js";
|
|
6
8
|
import { createTokensClient } from "./tokens/client.js";
|
|
7
9
|
import { readEnv } from "./utils/env.js";
|
|
8
|
-
import { createConsoleLogger, noopLogger } from "./utils/logger.js";
|
|
9
10
|
import { z } from "zod";
|
|
10
|
-
|
|
11
11
|
//#region src/index.ts
|
|
12
12
|
const proxySchema = z.union([z.string().url(), z.string().startsWith("/")]);
|
|
13
13
|
const decartClientOptionsSchema = z.object({
|
|
@@ -31,6 +31,8 @@ const decartClientOptionsSchema = z.object({
|
|
|
31
31
|
* @param options.realtimeBaseUrl - Override the default WebSocket base URL for realtime connections.
|
|
32
32
|
* @param options.integration - Optional integration identifier.
|
|
33
33
|
*
|
|
34
|
+
* Realtime media uses LiveKit only (inference must enable it in `TRANSPORTS_ENABLED`).
|
|
35
|
+
*
|
|
34
36
|
* @example
|
|
35
37
|
* ```ts
|
|
36
38
|
* // (direct API access)Option 1: Explicit API key
|
|
@@ -59,33 +61,46 @@ const createDecartClient = (options = {}) => {
|
|
|
59
61
|
if (isProxyMode && "proxy" in parsedOptions.data && parsedOptions.data.proxy) baseUrl = parsedOptions.data.proxy;
|
|
60
62
|
else baseUrl = parsedOptions.data.baseUrl || "https://api.decart.ai";
|
|
61
63
|
const { integration } = parsedOptions.data;
|
|
62
|
-
const logger = "logger" in options && options.logger ? options.logger :
|
|
63
|
-
const telemetryEnabled = "telemetry" in options && options.telemetry === false
|
|
64
|
+
const logger = "logger" in options && options.logger ? options.logger : createConsoleLogger("info");
|
|
65
|
+
const telemetryEnabled = !("telemetry" in options && options.telemetry === false);
|
|
66
|
+
const wsBaseUrl = parsedOptions.data.realtimeBaseUrl || "wss://api3.decart.ai";
|
|
67
|
+
const realtimePublish = createRealTimeClient({
|
|
68
|
+
baseUrl: wsBaseUrl,
|
|
69
|
+
apiKey: apiKey || "",
|
|
70
|
+
integration,
|
|
71
|
+
logger,
|
|
72
|
+
telemetryEnabled
|
|
73
|
+
});
|
|
74
|
+
const realtimeSubscribe = createRealTimeSubscribeClient({
|
|
75
|
+
baseUrl: isProxyMode || parsedOptions.data.baseUrl !== void 0 ? baseUrl : wsBaseUrl.replace(/^wss?:\/\//i, "https://"),
|
|
76
|
+
apiKey: apiKey || "",
|
|
77
|
+
integration,
|
|
78
|
+
logger
|
|
79
|
+
});
|
|
80
|
+
const process = createProcessClient({
|
|
81
|
+
baseUrl,
|
|
82
|
+
apiKey: apiKey || "",
|
|
83
|
+
integration
|
|
84
|
+
});
|
|
85
|
+
const queue = createQueueClient({
|
|
86
|
+
baseUrl,
|
|
87
|
+
apiKey: apiKey || "",
|
|
88
|
+
integration
|
|
89
|
+
});
|
|
90
|
+
const tokens = createTokensClient({
|
|
91
|
+
baseUrl,
|
|
92
|
+
apiKey: apiKey || "",
|
|
93
|
+
integration
|
|
94
|
+
});
|
|
64
95
|
return {
|
|
65
|
-
realtime:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
process: createProcessClient({
|
|
73
|
-
baseUrl,
|
|
74
|
-
apiKey: apiKey || "",
|
|
75
|
-
integration
|
|
76
|
-
}),
|
|
77
|
-
queue: createQueueClient({
|
|
78
|
-
baseUrl,
|
|
79
|
-
apiKey: apiKey || "",
|
|
80
|
-
integration
|
|
81
|
-
}),
|
|
82
|
-
tokens: createTokensClient({
|
|
83
|
-
baseUrl,
|
|
84
|
-
apiKey: apiKey || "",
|
|
85
|
-
integration
|
|
86
|
-
})
|
|
96
|
+
realtime: {
|
|
97
|
+
connect: realtimePublish.connect,
|
|
98
|
+
subscribe: realtimeSubscribe.subscribe
|
|
99
|
+
},
|
|
100
|
+
process,
|
|
101
|
+
queue,
|
|
102
|
+
tokens
|
|
87
103
|
};
|
|
88
104
|
};
|
|
89
|
-
|
|
90
105
|
//#endregion
|
|
91
|
-
export { ERROR_CODES, createConsoleLogger, createDecartClient, isCanonicalModel, isImageModel, isModel, isRealtimeModel, isVideoModel, listModels, modelAliases, models, noopLogger, resolveCanonicalModelAlias, resolveModelAlias };
|
|
106
|
+
export { ERROR_CODES, createConsoleLogger, createDecartClient, isCanonicalModel, isImageModel, isModel, isRealtimeModel, isVideoModel, listModels, modelAliases, models, noopLogger, resolveCanonicalModelAlias, resolveModelAlias };
|
package/dist/process/client.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createInvalidInputError } from "../utils/errors.js";
|
|
2
2
|
import { fileInputToBlob } from "../shared/request.js";
|
|
3
3
|
import { sendRequest } from "./request.js";
|
|
4
|
-
|
|
5
4
|
//#region src/process/client.ts
|
|
6
5
|
const createProcessClient = (opts) => {
|
|
7
6
|
const { apiKey, baseUrl, integration } = opts;
|
|
@@ -23,6 +22,5 @@ const createProcessClient = (opts) => {
|
|
|
23
22
|
};
|
|
24
23
|
return _process;
|
|
25
24
|
};
|
|
26
|
-
|
|
27
25
|
//#endregion
|
|
28
|
-
export { createProcessClient };
|
|
26
|
+
export { createProcessClient };
|
package/dist/process/request.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { createSDKError } from "../utils/errors.js";
|
|
2
2
|
import { buildAuthHeaders, buildFormData } from "../shared/request.js";
|
|
3
|
-
|
|
4
3
|
//#region src/process/request.ts
|
|
5
4
|
async function sendRequest({ baseUrl, apiKey, model, inputs, signal, integration }) {
|
|
6
5
|
const formData = buildFormData(inputs);
|
|
@@ -21,6 +20,5 @@ async function sendRequest({ baseUrl, apiKey, model, inputs, signal, integration
|
|
|
21
20
|
}
|
|
22
21
|
return response.blob();
|
|
23
22
|
}
|
|
24
|
-
|
|
25
23
|
//#endregion
|
|
26
|
-
export { sendRequest };
|
|
24
|
+
export { sendRequest };
|
package/dist/queue/client.js
CHANGED
|
@@ -2,7 +2,6 @@ import { createInvalidInputError } from "../utils/errors.js";
|
|
|
2
2
|
import { fileInputToBlob } from "../shared/request.js";
|
|
3
3
|
import { pollUntilComplete } from "./polling.js";
|
|
4
4
|
import { getJobContent, getJobStatus, submitJob } from "./request.js";
|
|
5
|
-
|
|
6
5
|
//#region src/queue/client.ts
|
|
7
6
|
const createQueueClient = (opts) => {
|
|
8
7
|
const { apiKey, baseUrl, integration } = opts;
|
|
@@ -68,6 +67,5 @@ const createQueueClient = (opts) => {
|
|
|
68
67
|
submitAndPoll
|
|
69
68
|
};
|
|
70
69
|
};
|
|
71
|
-
|
|
72
70
|
//#endregion
|
|
73
|
-
export { createQueueClient };
|
|
71
|
+
export { createQueueClient };
|
package/dist/queue/polling.js
CHANGED
package/dist/queue/request.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { createQueueResultError, createQueueStatusError, createQueueSubmitError } from "../utils/errors.js";
|
|
2
2
|
import { buildAuthHeaders, buildFormData } from "../shared/request.js";
|
|
3
|
-
|
|
4
3
|
//#region src/queue/request.ts
|
|
5
4
|
/**
|
|
6
5
|
* Submit a job to the queue.
|
|
@@ -68,6 +67,5 @@ async function getJobContent({ baseUrl, apiKey, jobId, signal, integration }) {
|
|
|
68
67
|
}
|
|
69
68
|
return response.blob();
|
|
70
69
|
}
|
|
71
|
-
|
|
72
70
|
//#endregion
|
|
73
|
-
export { getJobContent, getJobStatus, submitJob };
|
|
71
|
+
export { getJobContent, getJobStatus, submitJob };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { CustomModelDefinition, ModelDefinition } from "../shared/model.js";
|
|
2
2
|
import { DecartSDKError } from "../utils/errors.js";
|
|
3
|
-
import { ConnectionState } from "./types.js";
|
|
4
3
|
import { DiagnosticEvent } from "./observability/diagnostics.js";
|
|
5
4
|
import { WebRTCStats } from "./observability/webrtc-stats.js";
|
|
5
|
+
import { ConnectionState, GenerationEnded, GenerationTick, ImageSetOptions, QueuePosition } from "./types.js";
|
|
6
6
|
import { SetInput } from "./methods.js";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
|
|
@@ -16,18 +16,27 @@ declare const realTimeClientInitialStateSchema: z.ZodObject<{
|
|
|
16
16
|
image: z.ZodOptional<z.ZodUnion<readonly [z.ZodCustom<Blob, Blob>, z.ZodCustom<File, File>, z.ZodString]>>;
|
|
17
17
|
}, z.core.$strip>;
|
|
18
18
|
type OnRemoteStreamFn = (stream: MediaStream) => void;
|
|
19
|
+
type OnConnectionChangeFn = (state: ConnectionState) => void;
|
|
20
|
+
type OnQueuePositionFn = (queuePosition: QueuePosition) => void;
|
|
19
21
|
type RealTimeClientInitialState = z.infer<typeof realTimeClientInitialStateSchema>;
|
|
20
22
|
declare const realTimeClientConnectOptionsSchema: z.ZodObject<{
|
|
21
23
|
model: z.ZodObject<{
|
|
22
24
|
name: z.ZodString;
|
|
23
25
|
urlPath: z.ZodString;
|
|
24
26
|
queueUrlPath: z.ZodOptional<z.ZodString>;
|
|
25
|
-
fps: z.ZodNumber
|
|
27
|
+
fps: z.ZodUnion<readonly [z.ZodNumber, z.ZodObject<{
|
|
28
|
+
max: z.ZodOptional<z.ZodNumber>;
|
|
29
|
+
min: z.ZodOptional<z.ZodNumber>;
|
|
30
|
+
ideal: z.ZodOptional<z.ZodNumber>;
|
|
31
|
+
exact: z.ZodOptional<z.ZodNumber>;
|
|
32
|
+
}, z.core.$strip>]>;
|
|
26
33
|
width: z.ZodNumber;
|
|
27
34
|
height: z.ZodNumber;
|
|
28
35
|
inputSchema: z.ZodOptional<z.ZodAny>;
|
|
29
36
|
}, z.core.$strip>;
|
|
30
37
|
onRemoteStream: z.ZodCustom<OnRemoteStreamFn, OnRemoteStreamFn>;
|
|
38
|
+
onConnectionChange: z.ZodOptional<z.ZodCustom<OnConnectionChangeFn, OnConnectionChangeFn>>;
|
|
39
|
+
onQueuePosition: z.ZodOptional<z.ZodCustom<OnQueuePositionFn, OnQueuePositionFn>>;
|
|
31
40
|
initialState: z.ZodOptional<z.ZodObject<{
|
|
32
41
|
prompt: z.ZodOptional<z.ZodObject<{
|
|
33
42
|
text: z.ZodString;
|
|
@@ -35,7 +44,7 @@ declare const realTimeClientConnectOptionsSchema: z.ZodObject<{
|
|
|
35
44
|
}, z.core.$strip>>;
|
|
36
45
|
image: z.ZodOptional<z.ZodUnion<readonly [z.ZodCustom<Blob, Blob>, z.ZodCustom<File, File>, z.ZodString]>>;
|
|
37
46
|
}, z.core.$strip>>;
|
|
38
|
-
|
|
47
|
+
queryParams: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
39
48
|
mirror: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"auto">, z.ZodBoolean]>>;
|
|
40
49
|
resolution: z.ZodOptional<z.ZodEnum<{
|
|
41
50
|
"720p": "720p";
|
|
@@ -47,10 +56,10 @@ type RealTimeClientConnectOptions = Omit<z.infer<typeof realTimeClientConnectOpt
|
|
|
47
56
|
};
|
|
48
57
|
type Events = {
|
|
49
58
|
connectionChange: ConnectionState;
|
|
59
|
+
queuePosition: QueuePosition;
|
|
50
60
|
error: DecartSDKError;
|
|
51
|
-
generationTick:
|
|
52
|
-
|
|
53
|
-
};
|
|
61
|
+
generationTick: GenerationTick;
|
|
62
|
+
generationEnded: GenerationEnded;
|
|
54
63
|
diagnostic: DiagnosticEvent;
|
|
55
64
|
stats: WebRTCStats;
|
|
56
65
|
};
|
|
@@ -68,11 +77,8 @@ type RealTimeClient = {
|
|
|
68
77
|
off: <K extends keyof Events>(event: K, listener: (data: Events[K]) => void) => void;
|
|
69
78
|
sessionId: string | null;
|
|
70
79
|
subscribeToken: string | null;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
enhance?: boolean;
|
|
74
|
-
timeout?: number;
|
|
75
|
-
}) => Promise<void>;
|
|
80
|
+
getSubscribeToken: () => string | null;
|
|
81
|
+
setImage: (image: Blob | File | string | null, options?: ImageSetOptions) => Promise<void>;
|
|
76
82
|
};
|
|
77
83
|
//#endregion
|
|
78
84
|
export { Events, RealTimeClient, RealTimeClientConnectOptions, RealTimeClientInitialState };
|
package/dist/realtime/client.js
CHANGED
|
@@ -1,94 +1,48 @@
|
|
|
1
1
|
import { classifyWebrtcError } from "../utils/errors.js";
|
|
2
|
-
import { modelDefinitionSchema } from "../shared/model.js";
|
|
2
|
+
import { modelDefinitionSchema, resolveFpsNumber } from "../shared/model.js";
|
|
3
3
|
import { modelStateSchema } from "../shared/types.js";
|
|
4
|
+
import { createConsoleLogger } from "../utils/logger.js";
|
|
5
|
+
import { imageToBase64 } from "../utils/media.js";
|
|
6
|
+
import { isDesktopSafari } from "../utils/platform.js";
|
|
4
7
|
import { createEventBuffer } from "./event-buffer.js";
|
|
5
8
|
import { realtimeMethods } from "./methods.js";
|
|
6
9
|
import { createMirroredStream, shouldMirrorTrack } from "./mirror-stream.js";
|
|
7
10
|
import { RealtimeObservability } from "./observability/realtime-observability.js";
|
|
8
|
-
import {
|
|
9
|
-
import { WebRTCManager } from "./webrtc-manager.js";
|
|
11
|
+
import { StreamSession } from "./stream-session.js";
|
|
10
12
|
import { z } from "zod";
|
|
11
|
-
|
|
12
13
|
//#region src/realtime/client.ts
|
|
13
|
-
async function blobToBase64(blob) {
|
|
14
|
-
return new Promise((resolve, reject) => {
|
|
15
|
-
const reader = new FileReader();
|
|
16
|
-
reader.onloadend = () => {
|
|
17
|
-
const result = reader.result;
|
|
18
|
-
if (typeof result !== "string") {
|
|
19
|
-
reject(/* @__PURE__ */ new Error("FileReader did not return a string"));
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
const base64 = result.split(",")[1];
|
|
23
|
-
if (!base64) {
|
|
24
|
-
reject(/* @__PURE__ */ new Error("Invalid data URL format"));
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
resolve(base64);
|
|
28
|
-
};
|
|
29
|
-
reader.onerror = reject;
|
|
30
|
-
reader.readAsDataURL(blob);
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
async function imageToBase64(image) {
|
|
34
|
-
if (typeof image === "string") {
|
|
35
|
-
let url = null;
|
|
36
|
-
try {
|
|
37
|
-
url = new URL(image);
|
|
38
|
-
} catch {}
|
|
39
|
-
if (url?.protocol === "data:") {
|
|
40
|
-
const [, base64] = image.split(",", 2);
|
|
41
|
-
if (!base64) throw new Error("Invalid data URL image");
|
|
42
|
-
return base64;
|
|
43
|
-
}
|
|
44
|
-
if (url?.protocol === "http:" || url?.protocol === "https:") {
|
|
45
|
-
const response = await fetch(image);
|
|
46
|
-
if (!response.ok) throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`);
|
|
47
|
-
return blobToBase64(await response.blob());
|
|
48
|
-
}
|
|
49
|
-
return image;
|
|
50
|
-
}
|
|
51
|
-
return blobToBase64(image);
|
|
52
|
-
}
|
|
53
14
|
const realTimeClientInitialStateSchema = modelStateSchema;
|
|
54
|
-
const createAsyncFunctionSchema = (schema) => z.custom((fn) => schema.implementAsync(fn));
|
|
55
15
|
const realTimeClientConnectOptionsSchema = z.object({
|
|
56
16
|
model: modelDefinitionSchema,
|
|
57
17
|
onRemoteStream: z.custom((val) => typeof val === "function", { message: "onRemoteStream must be a function" }),
|
|
18
|
+
onConnectionChange: z.custom((val) => typeof val === "function", { message: "onConnectionChange must be a function" }).optional(),
|
|
19
|
+
onQueuePosition: z.custom((val) => typeof val === "function", { message: "onQueuePosition must be a function" }).optional(),
|
|
58
20
|
initialState: realTimeClientInitialStateSchema.optional(),
|
|
59
|
-
|
|
21
|
+
queryParams: z.record(z.string(), z.string()).optional(),
|
|
60
22
|
mirror: z.union([z.literal("auto"), z.boolean()]).optional(),
|
|
61
23
|
resolution: z.enum(["720p", "1080p"]).optional()
|
|
62
24
|
});
|
|
63
25
|
const createRealTimeClient = (opts) => {
|
|
64
|
-
const { baseUrl, apiKey, integration
|
|
26
|
+
const { baseUrl, apiKey, integration } = opts;
|
|
27
|
+
const logger = opts.logger ?? createConsoleLogger("info");
|
|
65
28
|
const connect = async (stream, options) => {
|
|
66
29
|
const parsedOptions = realTimeClientConnectOptionsSchema.safeParse(options);
|
|
67
30
|
if (!parsedOptions.success) throw parsedOptions.error;
|
|
68
|
-
const { onRemoteStream, initialState, resolution } = parsedOptions.data;
|
|
31
|
+
const { onRemoteStream, onConnectionChange, onQueuePosition, initialState, resolution } = parsedOptions.data;
|
|
69
32
|
const mirror = parsedOptions.data.mirror ?? false;
|
|
70
33
|
let inputStream = stream ?? new MediaStream();
|
|
71
34
|
let mirroredStream;
|
|
72
35
|
if (mirror !== false) try {
|
|
73
36
|
const firstVideoTrack = inputStream.getVideoTracks?.()[0];
|
|
74
37
|
if (firstVideoTrack && (mirror === true || shouldMirrorTrack(firstVideoTrack))) {
|
|
75
|
-
mirroredStream = createMirroredStream(inputStream, { fps: options.model.fps });
|
|
38
|
+
mirroredStream = createMirroredStream(inputStream, { fps: resolveFpsNumber(options.model.fps) });
|
|
76
39
|
inputStream = mirroredStream.stream;
|
|
77
40
|
} else if (mirror === true && !firstVideoTrack) logger.warn("mirror: true requested but no video track was found on the input stream");
|
|
78
41
|
} catch (error) {
|
|
79
42
|
logger.warn("Failed to mirror input stream; falling back to un-mirrored input", { error: error instanceof Error ? error.message : String(error) });
|
|
80
43
|
}
|
|
81
|
-
let
|
|
82
|
-
|
|
83
|
-
const observability = new RealtimeObservability({
|
|
84
|
-
telemetryEnabled: opts.telemetryEnabled,
|
|
85
|
-
apiKey,
|
|
86
|
-
model: options.model.name,
|
|
87
|
-
integration,
|
|
88
|
-
logger,
|
|
89
|
-
onDiagnostic: (event) => emitOrBuffer("diagnostic", event),
|
|
90
|
-
onStats: opts.telemetryEnabled ? (stats) => emitOrBuffer("stats", stats) : void 0
|
|
91
|
-
});
|
|
44
|
+
let session;
|
|
45
|
+
let observability;
|
|
92
46
|
try {
|
|
93
47
|
const initialImage = initialState?.image ? await imageToBase64(initialState.image) : void 0;
|
|
94
48
|
const initialPrompt = initialState?.prompt ? {
|
|
@@ -96,50 +50,65 @@ const createRealTimeClient = (opts) => {
|
|
|
96
50
|
enhance: initialState.prompt.enhance
|
|
97
51
|
} : void 0;
|
|
98
52
|
const url = `${baseUrl}${options.model.urlPath}`;
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
53
|
+
const { emitter: eventEmitter, emitOrBuffer, flush, stop } = createEventBuffer();
|
|
54
|
+
observability = new RealtimeObservability({
|
|
55
|
+
telemetryEnabled: opts.telemetryEnabled,
|
|
56
|
+
apiKey,
|
|
57
|
+
model: options.model.name,
|
|
102
58
|
integration,
|
|
103
59
|
logger,
|
|
60
|
+
onDiagnostic: (event) => emitOrBuffer("diagnostic", event),
|
|
61
|
+
onStats: (stats) => emitOrBuffer("stats", stats)
|
|
62
|
+
});
|
|
63
|
+
const safariCodec = isDesktopSafari() ? "vp8" : void 0;
|
|
64
|
+
session = new StreamSession({
|
|
65
|
+
url: `${url}?${new URLSearchParams({
|
|
66
|
+
...safariCodec ? { livekit_server_codec: safariCodec } : {},
|
|
67
|
+
...options.queryParams ?? {},
|
|
68
|
+
api_key: apiKey,
|
|
69
|
+
model: options.model.name,
|
|
70
|
+
...resolution ? { resolution } : {}
|
|
71
|
+
}).toString()}`,
|
|
72
|
+
integration,
|
|
104
73
|
observability,
|
|
105
|
-
|
|
106
|
-
onConnectionStateChange: (state) => {
|
|
107
|
-
emitOrBuffer("connectionChange", state);
|
|
108
|
-
},
|
|
109
|
-
onError: (error) => {
|
|
110
|
-
logger.error("WebRTC error", { error: error.message });
|
|
111
|
-
emitOrBuffer("error", classifyWebrtcError(error));
|
|
112
|
-
},
|
|
113
|
-
customizeOffer: options.customizeOffer,
|
|
114
|
-
vp8MinBitrate: 300,
|
|
115
|
-
vp8StartBitrate: 600,
|
|
74
|
+
localStream: inputStream,
|
|
116
75
|
initialImage,
|
|
117
|
-
initialPrompt
|
|
76
|
+
initialPrompt,
|
|
77
|
+
logger,
|
|
78
|
+
videoCodec: safariCodec
|
|
118
79
|
});
|
|
119
|
-
const manager = webrtcManager;
|
|
120
80
|
let sessionId = null;
|
|
121
81
|
let subscribeToken = null;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
82
|
+
session.on("remoteStream", onRemoteStream);
|
|
83
|
+
session.on("connectionChange", (state) => {
|
|
84
|
+
emitOrBuffer("connectionChange", state);
|
|
85
|
+
onConnectionChange?.(state);
|
|
86
|
+
});
|
|
87
|
+
session.on("queuePosition", (qp) => {
|
|
88
|
+
emitOrBuffer("queuePosition", qp);
|
|
89
|
+
onQueuePosition?.(qp);
|
|
90
|
+
});
|
|
91
|
+
session.on("sessionStarted", ({ sessionId: id, subscribeToken: token }) => {
|
|
92
|
+
sessionId = id;
|
|
93
|
+
subscribeToken = token;
|
|
94
|
+
observability?.sessionStarted(id);
|
|
95
|
+
});
|
|
96
|
+
session.on("generationTick", (e) => emitOrBuffer("generationTick", e));
|
|
97
|
+
session.on("generationEnded", (e) => emitOrBuffer("generationEnded", e));
|
|
98
|
+
session.on("error", (error) => {
|
|
99
|
+
logger.error("Realtime error", { error: error.message });
|
|
100
|
+
emitOrBuffer("error", classifyWebrtcError(error));
|
|
101
|
+
});
|
|
102
|
+
const activeSession = session;
|
|
103
|
+
await activeSession.connect();
|
|
134
104
|
const client = {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
getConnectionState: () => manager.getConnectionState(),
|
|
105
|
+
...realtimeMethods(activeSession, imageToBase64),
|
|
106
|
+
isConnected: () => activeSession.isConnected(),
|
|
107
|
+
getConnectionState: () => activeSession.getConnectionState(),
|
|
139
108
|
disconnect: () => {
|
|
140
|
-
observability
|
|
109
|
+
observability?.stop();
|
|
141
110
|
stop();
|
|
142
|
-
|
|
111
|
+
activeSession.disconnect();
|
|
143
112
|
mirroredStream?.dispose();
|
|
144
113
|
},
|
|
145
114
|
on: eventEmitter.on,
|
|
@@ -150,76 +119,23 @@ const createRealTimeClient = (opts) => {
|
|
|
150
119
|
get subscribeToken() {
|
|
151
120
|
return subscribeToken;
|
|
152
121
|
},
|
|
153
|
-
|
|
154
|
-
|
|
122
|
+
getSubscribeToken: () => subscribeToken,
|
|
123
|
+
setImage: async (image, imgOptions) => {
|
|
124
|
+
if (image === null) return activeSession.setImage(null, imgOptions);
|
|
155
125
|
const base64 = await imageToBase64(image);
|
|
156
|
-
return
|
|
126
|
+
return activeSession.setImage(base64, imgOptions);
|
|
157
127
|
}
|
|
158
128
|
};
|
|
159
129
|
flush();
|
|
160
130
|
return client;
|
|
161
131
|
} catch (error) {
|
|
162
|
-
observability
|
|
163
|
-
|
|
132
|
+
observability?.stop();
|
|
133
|
+
session?.disconnect();
|
|
164
134
|
mirroredStream?.dispose();
|
|
165
135
|
throw error;
|
|
166
136
|
}
|
|
167
137
|
};
|
|
168
|
-
|
|
169
|
-
const { sid, ip, port } = decodeSubscribeToken(options.token);
|
|
170
|
-
const subscribeUrl = `${baseUrl}/subscribe/${encodeURIComponent(sid)}?IP=${encodeURIComponent(ip)}&port=${encodeURIComponent(port)}&api_key=${encodeURIComponent(apiKey)}`;
|
|
171
|
-
const { emitter: eventEmitter, emitOrBuffer, flush, stop } = createEventBuffer();
|
|
172
|
-
let webrtcManager;
|
|
173
|
-
const observability = new RealtimeObservability({
|
|
174
|
-
telemetryEnabled: opts.telemetryEnabled,
|
|
175
|
-
apiKey,
|
|
176
|
-
integration,
|
|
177
|
-
logger,
|
|
178
|
-
onDiagnostic: (event) => emitOrBuffer("diagnostic", event),
|
|
179
|
-
onStats: opts.telemetryEnabled ? (stats) => emitOrBuffer("stats", stats) : void 0
|
|
180
|
-
});
|
|
181
|
-
observability.sessionStarted(sid);
|
|
182
|
-
try {
|
|
183
|
-
webrtcManager = new WebRTCManager({
|
|
184
|
-
webrtcUrl: subscribeUrl,
|
|
185
|
-
integration,
|
|
186
|
-
logger,
|
|
187
|
-
observability,
|
|
188
|
-
onRemoteStream: options.onRemoteStream,
|
|
189
|
-
onConnectionStateChange: (state) => {
|
|
190
|
-
emitOrBuffer("connectionChange", state);
|
|
191
|
-
},
|
|
192
|
-
onError: (error) => {
|
|
193
|
-
logger.error("WebRTC subscribe error", { error: error.message });
|
|
194
|
-
emitOrBuffer("error", classifyWebrtcError(error));
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
const manager = webrtcManager;
|
|
198
|
-
await manager.connect(null);
|
|
199
|
-
const client = {
|
|
200
|
-
isConnected: () => manager.isConnected(),
|
|
201
|
-
getConnectionState: () => manager.getConnectionState(),
|
|
202
|
-
disconnect: () => {
|
|
203
|
-
observability.stop();
|
|
204
|
-
stop();
|
|
205
|
-
manager.cleanup();
|
|
206
|
-
},
|
|
207
|
-
on: eventEmitter.on,
|
|
208
|
-
off: eventEmitter.off
|
|
209
|
-
};
|
|
210
|
-
flush();
|
|
211
|
-
return client;
|
|
212
|
-
} catch (error) {
|
|
213
|
-
observability.stop();
|
|
214
|
-
webrtcManager?.cleanup();
|
|
215
|
-
throw error;
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
return {
|
|
219
|
-
connect,
|
|
220
|
-
subscribe
|
|
221
|
-
};
|
|
138
|
+
return { connect };
|
|
222
139
|
};
|
|
223
|
-
|
|
224
140
|
//#endregion
|
|
225
|
-
export { createRealTimeClient };
|
|
141
|
+
export { createRealTimeClient };
|