@decartai/sdk 0.0.18 → 0.0.20
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/dist/index.d.ts +41 -1
- package/dist/index.js +8 -1
- package/dist/process/client.js +2 -1
- package/dist/process/request.js +5 -23
- package/dist/process/types.d.ts +1 -1
- package/dist/queue/client.d.ts +65 -0
- package/dist/queue/client.js +73 -0
- package/dist/queue/polling.js +35 -0
- package/dist/queue/request.js +61 -0
- package/dist/queue/types.d.ts +79 -0
- package/dist/realtime/client.d.ts +4 -3
- package/dist/realtime/methods.js +26 -6
- package/dist/realtime/webrtc-connection.js +5 -0
- package/dist/realtime/webrtc-manager.js +3 -0
- package/dist/shared/model.d.ts +1 -0
- package/dist/shared/model.js +10 -0
- package/dist/shared/request.js +41 -0
- package/dist/utils/errors.d.ts +4 -0
- package/dist/utils/errors.js +15 -2
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { ImageModels, Model, ModelDefinition, RealTimeModels, VideoModels, models } from "./shared/model.js";
|
|
2
2
|
import { FileInput, ProcessOptions } from "./process/types.js";
|
|
3
3
|
import { ProcessClient } from "./process/client.js";
|
|
4
|
+
import { JobStatus, JobStatusResponse, JobSubmitResponse, QueueJobResult, QueueSubmitAndPollOptions, QueueSubmitOptions } from "./queue/types.js";
|
|
5
|
+
import { QueueClient } from "./queue/client.js";
|
|
4
6
|
import { DecartSDKError, ERROR_CODES } from "./utils/errors.js";
|
|
5
7
|
import { RealTimeClient, RealTimeClientConnectOptions, RealTimeClientInitialState } from "./realtime/client.js";
|
|
6
8
|
import { ModelState } from "./shared/types.js";
|
|
@@ -30,6 +32,44 @@ declare const createDecartClient: (options: DecartClientOptions) => {
|
|
|
30
32
|
* ```
|
|
31
33
|
*/
|
|
32
34
|
process: ProcessClient;
|
|
35
|
+
/**
|
|
36
|
+
* Client for queue-based async video and image generation.
|
|
37
|
+
* Jobs are submitted and processed asynchronously.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const client = createDecartClient({ apiKey: "your-api-key" });
|
|
42
|
+
*
|
|
43
|
+
* // Option 1: Submit and poll automatically
|
|
44
|
+
* const result = await client.queue.submitAndPoll({
|
|
45
|
+
* model: models.video("lucy-pro-t2v"),
|
|
46
|
+
* prompt: "A beautiful sunset over the ocean",
|
|
47
|
+
* onStatusChange: (job) => console.log(`Job ${job.job_id}: ${job.status}`)
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* // Option 2: Submit and poll manually
|
|
51
|
+
* const job = await client.queue.submit({
|
|
52
|
+
* model: models.video("lucy-pro-t2v"),
|
|
53
|
+
* prompt: "A beautiful sunset over the ocean"
|
|
54
|
+
* });
|
|
55
|
+
*
|
|
56
|
+
* // Poll until completion
|
|
57
|
+
* while (true) {
|
|
58
|
+
* const status = await client.queue.status(job.job_id);
|
|
59
|
+
* console.log(`Job ${status.job_id}: ${status.status}`);
|
|
60
|
+
*
|
|
61
|
+
* if (status.status === "completed") {
|
|
62
|
+
* const blob = await client.queue.result(job.job_id);
|
|
63
|
+
* break;
|
|
64
|
+
* }
|
|
65
|
+
* if (status.status === "failed") {
|
|
66
|
+
* throw new Error("Job failed");
|
|
67
|
+
* }
|
|
68
|
+
* await new Promise(resolve => setTimeout(resolve, 1500));
|
|
69
|
+
* }
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
queue: QueueClient;
|
|
33
73
|
};
|
|
34
74
|
//#endregion
|
|
35
|
-
export { DecartClientOptions, type DecartSDKError, ERROR_CODES, type FileInput, type ImageModels, type Model, type ModelDefinition, type ModelState, type ProcessClient, type ProcessOptions, type RealTimeClient, type RealTimeClientConnectOptions, type RealTimeClientInitialState, type RealTimeModels, type VideoModels, createDecartClient, models };
|
|
75
|
+
export { DecartClientOptions, type DecartSDKError, ERROR_CODES, type FileInput, type ImageModels, type JobStatus, type JobStatusResponse, type JobSubmitResponse, type Model, type ModelDefinition, type ModelState, type ProcessClient, type ProcessOptions, type QueueClient, type QueueJobResult, type QueueSubmitAndPollOptions, type QueueSubmitOptions, type RealTimeClient, type RealTimeClientConnectOptions, type RealTimeClientInitialState, type RealTimeModels, type VideoModels, createDecartClient, models };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ERROR_CODES, createInvalidApiKeyError, createInvalidBaseUrlError } from "./utils/errors.js";
|
|
2
2
|
import { createProcessClient } from "./process/client.js";
|
|
3
|
+
import { createQueueClient } from "./queue/client.js";
|
|
3
4
|
import { models } from "./shared/model.js";
|
|
4
5
|
import { createRealTimeClient } from "./realtime/client.js";
|
|
5
6
|
import { z } from "zod";
|
|
@@ -29,9 +30,15 @@ const createDecartClient = (options) => {
|
|
|
29
30
|
apiKey,
|
|
30
31
|
integration
|
|
31
32
|
});
|
|
33
|
+
const queue = createQueueClient({
|
|
34
|
+
baseUrl,
|
|
35
|
+
apiKey,
|
|
36
|
+
integration
|
|
37
|
+
});
|
|
32
38
|
return {
|
|
33
39
|
realtime,
|
|
34
|
-
process
|
|
40
|
+
process,
|
|
41
|
+
queue
|
|
35
42
|
};
|
|
36
43
|
};
|
|
37
44
|
|
package/dist/process/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createInvalidInputError } from "../utils/errors.js";
|
|
2
|
-
import { fileInputToBlob
|
|
2
|
+
import { fileInputToBlob } from "../shared/request.js";
|
|
3
|
+
import { sendRequest } from "./request.js";
|
|
3
4
|
|
|
4
5
|
//#region src/process/client.ts
|
|
5
6
|
const createProcessClient = (opts) => {
|
package/dist/process/request.js
CHANGED
|
@@ -1,31 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { createSDKError } from "../utils/errors.js";
|
|
2
|
+
import { buildAuthHeaders, buildFormData } from "../shared/request.js";
|
|
3
3
|
|
|
4
4
|
//#region src/process/request.ts
|
|
5
|
-
async function fileInputToBlob(input) {
|
|
6
|
-
if (input instanceof Blob || input instanceof File) return input;
|
|
7
|
-
if (input instanceof ReadableStream) return new Response(input).blob();
|
|
8
|
-
if (typeof input === "string" || input instanceof URL) {
|
|
9
|
-
const url = typeof input === "string" ? input : input.toString();
|
|
10
|
-
if (!url.startsWith("http://") && !url.startsWith("https://")) throw createInvalidInputError("URL must start with http:// or https://");
|
|
11
|
-
const response = await fetch(url);
|
|
12
|
-
if (!response.ok) throw createInvalidInputError(`Failed to fetch file from URL: ${response.statusText}`);
|
|
13
|
-
return response.blob();
|
|
14
|
-
}
|
|
15
|
-
throw createInvalidInputError("Invalid file input type");
|
|
16
|
-
}
|
|
17
5
|
async function sendRequest({ baseUrl, apiKey, model, inputs, signal, integration }) {
|
|
18
|
-
const formData =
|
|
19
|
-
for (const [key, value] of Object.entries(inputs)) if (value !== void 0 && value !== null) if (value instanceof Blob) formData.append(key, value);
|
|
20
|
-
else if (typeof value === "object" && value !== null) formData.append(key, JSON.stringify(value));
|
|
21
|
-
else formData.append(key, String(value));
|
|
6
|
+
const formData = buildFormData(inputs);
|
|
22
7
|
const endpoint = `${baseUrl}${model.urlPath}`;
|
|
23
8
|
const response = await fetch(endpoint, {
|
|
24
9
|
method: "POST",
|
|
25
|
-
headers:
|
|
26
|
-
"X-API-KEY": apiKey,
|
|
27
|
-
"User-Agent": buildUserAgent(integration)
|
|
28
|
-
},
|
|
10
|
+
headers: buildAuthHeaders(apiKey, integration),
|
|
29
11
|
body: formData,
|
|
30
12
|
signal
|
|
31
13
|
});
|
|
@@ -37,4 +19,4 @@ async function sendRequest({ baseUrl, apiKey, model, inputs, signal, integration
|
|
|
37
19
|
}
|
|
38
20
|
|
|
39
21
|
//#endregion
|
|
40
|
-
export {
|
|
22
|
+
export { sendRequest };
|
package/dist/process/types.d.ts
CHANGED
|
@@ -142,4 +142,4 @@ type ProcessOptions<T extends ModelDefinition = ModelDefinition> = {
|
|
|
142
142
|
signal?: AbortSignal;
|
|
143
143
|
} & MergeDocumentedInputs<T>;
|
|
144
144
|
//#endregion
|
|
145
|
-
export { FileInput, ProcessOptions };
|
|
145
|
+
export { FileInput, InferModelInputs, ModelSpecificInputs, ProcessInputs, ProcessOptions };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ModelDefinition } from "../shared/model.js";
|
|
2
|
+
import { JobStatusResponse, JobSubmitResponse, QueueJobResult, QueueSubmitAndPollOptions, QueueSubmitOptions } from "./types.js";
|
|
3
|
+
|
|
4
|
+
//#region src/queue/client.d.ts
|
|
5
|
+
type QueueClient = {
|
|
6
|
+
/**
|
|
7
|
+
* Submit a job to the queue for async processing.
|
|
8
|
+
* Returns immediately with job_id and initial status.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const job = await client.queue.submit({
|
|
13
|
+
* model: models.video("lucy-pro-t2v"),
|
|
14
|
+
* prompt: "A cat playing piano"
|
|
15
|
+
* });
|
|
16
|
+
* console.log(job.job_id); // "job_abc123"
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
submit: <T extends ModelDefinition>(options: QueueSubmitOptions<T>) => Promise<JobSubmitResponse>;
|
|
20
|
+
/**
|
|
21
|
+
* Get the current status of a job.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* const status = await client.queue.status("job_abc123");
|
|
26
|
+
* console.log(status.status); // "pending" | "processing" | "completed" | "failed"
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
status: (jobId: string) => Promise<JobStatusResponse>;
|
|
30
|
+
/**
|
|
31
|
+
* Get the result of a completed job.
|
|
32
|
+
* Should only be called when job status is "completed".
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* const blob = await client.queue.result("job_abc123");
|
|
37
|
+
* videoElement.src = URL.createObjectURL(blob);
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
result: (jobId: string) => Promise<Blob>;
|
|
41
|
+
/**
|
|
42
|
+
* Submit a job and automatically poll until completion.
|
|
43
|
+
* Returns a result object with status (does not throw on failure).
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* const result = await client.queue.submitAndPoll({
|
|
48
|
+
* model: models.video("lucy-pro-t2v"),
|
|
49
|
+
* prompt: "A beautiful sunset",
|
|
50
|
+
* onStatusChange: (job) => {
|
|
51
|
+
* console.log(`Job ${job.job_id}: ${job.status}`);
|
|
52
|
+
* }
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* if (result.status === "completed") {
|
|
56
|
+
* videoElement.src = URL.createObjectURL(result.data);
|
|
57
|
+
* } else {
|
|
58
|
+
* console.error("Job failed:", result.error);
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
submitAndPoll: <T extends ModelDefinition>(options: QueueSubmitAndPollOptions<T>) => Promise<QueueJobResult>;
|
|
63
|
+
};
|
|
64
|
+
//#endregion
|
|
65
|
+
export { QueueClient };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createInvalidInputError } from "../utils/errors.js";
|
|
2
|
+
import { fileInputToBlob } from "../shared/request.js";
|
|
3
|
+
import { pollUntilComplete } from "./polling.js";
|
|
4
|
+
import { getJobContent, getJobStatus, submitJob } from "./request.js";
|
|
5
|
+
|
|
6
|
+
//#region src/queue/client.ts
|
|
7
|
+
const createQueueClient = (opts) => {
|
|
8
|
+
const { apiKey, baseUrl, integration } = opts;
|
|
9
|
+
const submit = async (options) => {
|
|
10
|
+
const { model, signal,...inputs } = options;
|
|
11
|
+
const parsedInputs = model.inputSchema.safeParse(inputs);
|
|
12
|
+
if (!parsedInputs.success) throw createInvalidInputError(`Invalid inputs for ${model.name}: ${parsedInputs.error.message}`);
|
|
13
|
+
const processedInputs = {};
|
|
14
|
+
for (const [key, value] of Object.entries(parsedInputs.data)) if (key === "data" || key === "start" || key === "end") processedInputs[key] = await fileInputToBlob(value);
|
|
15
|
+
else processedInputs[key] = value;
|
|
16
|
+
return submitJob({
|
|
17
|
+
baseUrl,
|
|
18
|
+
apiKey,
|
|
19
|
+
model,
|
|
20
|
+
inputs: processedInputs,
|
|
21
|
+
signal,
|
|
22
|
+
integration
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
const status = async (jobId) => {
|
|
26
|
+
return getJobStatus({
|
|
27
|
+
baseUrl,
|
|
28
|
+
apiKey,
|
|
29
|
+
jobId,
|
|
30
|
+
integration
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
const result = async (jobId) => {
|
|
34
|
+
return getJobContent({
|
|
35
|
+
baseUrl,
|
|
36
|
+
apiKey,
|
|
37
|
+
jobId,
|
|
38
|
+
integration
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
const submitAndPoll = async (options) => {
|
|
42
|
+
const { onStatusChange, signal,...submitOptions } = options;
|
|
43
|
+
const job = await submit(submitOptions);
|
|
44
|
+
if (onStatusChange) onStatusChange(job);
|
|
45
|
+
return pollUntilComplete({
|
|
46
|
+
checkStatus: () => getJobStatus({
|
|
47
|
+
baseUrl,
|
|
48
|
+
apiKey,
|
|
49
|
+
jobId: job.job_id,
|
|
50
|
+
signal,
|
|
51
|
+
integration
|
|
52
|
+
}),
|
|
53
|
+
getContent: () => getJobContent({
|
|
54
|
+
baseUrl,
|
|
55
|
+
apiKey,
|
|
56
|
+
jobId: job.job_id,
|
|
57
|
+
signal,
|
|
58
|
+
integration
|
|
59
|
+
}),
|
|
60
|
+
onStatusChange,
|
|
61
|
+
signal
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
submit,
|
|
66
|
+
status,
|
|
67
|
+
result,
|
|
68
|
+
submitAndPoll
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
export { createQueueClient };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//#region src/queue/polling.ts
|
|
2
|
+
const POLLING_DEFAULTS = {
|
|
3
|
+
interval: 1500,
|
|
4
|
+
initialDelay: 500
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Sleep for a given number of milliseconds.
|
|
8
|
+
*/
|
|
9
|
+
function sleep(ms) {
|
|
10
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Poll until the job is completed or failed.
|
|
14
|
+
* No timeout - backend returns "failed" after 10 minutes.
|
|
15
|
+
*/
|
|
16
|
+
async function pollUntilComplete({ checkStatus, getContent, onStatusChange, signal }) {
|
|
17
|
+
await sleep(POLLING_DEFAULTS.initialDelay);
|
|
18
|
+
while (true) {
|
|
19
|
+
if (signal?.aborted) throw new Error("Polling aborted");
|
|
20
|
+
const status = await checkStatus();
|
|
21
|
+
if (onStatusChange) onStatusChange(status);
|
|
22
|
+
if (status.status === "completed") return {
|
|
23
|
+
status: "completed",
|
|
24
|
+
data: await getContent()
|
|
25
|
+
};
|
|
26
|
+
if (status.status === "failed") return {
|
|
27
|
+
status: "failed",
|
|
28
|
+
error: "Job failed"
|
|
29
|
+
};
|
|
30
|
+
await sleep(POLLING_DEFAULTS.interval);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
export { pollUntilComplete };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createQueueResultError, createQueueStatusError, createQueueSubmitError } from "../utils/errors.js";
|
|
2
|
+
import { buildAuthHeaders, buildFormData } from "../shared/request.js";
|
|
3
|
+
|
|
4
|
+
//#region src/queue/request.ts
|
|
5
|
+
/**
|
|
6
|
+
* Submit a job to the queue.
|
|
7
|
+
* POST /v1/jobs/{model}
|
|
8
|
+
*/
|
|
9
|
+
async function submitJob({ baseUrl, apiKey, model, inputs, signal, integration }) {
|
|
10
|
+
const formData = buildFormData(inputs);
|
|
11
|
+
if (!model.queueUrlPath) throw createQueueSubmitError(`Model ${model.name} does not support queue processing`, 400);
|
|
12
|
+
const endpoint = `${baseUrl}${model.queueUrlPath}`;
|
|
13
|
+
const response = await fetch(endpoint, {
|
|
14
|
+
method: "POST",
|
|
15
|
+
headers: buildAuthHeaders(apiKey, integration),
|
|
16
|
+
body: formData,
|
|
17
|
+
signal
|
|
18
|
+
});
|
|
19
|
+
if (!response.ok) {
|
|
20
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
21
|
+
throw createQueueSubmitError(`Failed to submit job: ${response.status} - ${errorText}`, response.status);
|
|
22
|
+
}
|
|
23
|
+
return response.json();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Get the status of a job.
|
|
27
|
+
* GET /v1/jobs/{job_id}
|
|
28
|
+
*/
|
|
29
|
+
async function getJobStatus({ baseUrl, apiKey, jobId, signal, integration }) {
|
|
30
|
+
const endpoint = `${baseUrl}/v1/jobs/${jobId}`;
|
|
31
|
+
const response = await fetch(endpoint, {
|
|
32
|
+
method: "GET",
|
|
33
|
+
headers: buildAuthHeaders(apiKey, integration),
|
|
34
|
+
signal
|
|
35
|
+
});
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
38
|
+
throw createQueueStatusError(`Failed to get job status: ${response.status} - ${errorText}`, response.status);
|
|
39
|
+
}
|
|
40
|
+
return response.json();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get the content/result of a completed job.
|
|
44
|
+
* GET /v1/jobs/{job_id}/content
|
|
45
|
+
*/
|
|
46
|
+
async function getJobContent({ baseUrl, apiKey, jobId, signal, integration }) {
|
|
47
|
+
const endpoint = `${baseUrl}/v1/jobs/${jobId}/content`;
|
|
48
|
+
const response = await fetch(endpoint, {
|
|
49
|
+
method: "GET",
|
|
50
|
+
headers: buildAuthHeaders(apiKey, integration),
|
|
51
|
+
signal
|
|
52
|
+
});
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
55
|
+
throw createQueueResultError(`Failed to get job content: ${response.status} - ${errorText}`, response.status);
|
|
56
|
+
}
|
|
57
|
+
return response.blob();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
export { getJobContent, getJobStatus, submitJob };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { ModelDefinition } from "../shared/model.js";
|
|
2
|
+
import { FileInput, InferModelInputs, ModelSpecificInputs, ProcessInputs } from "../process/types.js";
|
|
3
|
+
|
|
4
|
+
//#region src/queue/types.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Job status values returned by the queue API.
|
|
8
|
+
*/
|
|
9
|
+
type JobStatus = "pending" | "processing" | "completed" | "failed";
|
|
10
|
+
/**
|
|
11
|
+
* Response from POST /v1/jobs/{model} - job submission.
|
|
12
|
+
*/
|
|
13
|
+
type JobSubmitResponse = {
|
|
14
|
+
job_id: string;
|
|
15
|
+
status: JobStatus;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Response from GET /v1/jobs/{job_id} - job status check.
|
|
19
|
+
*/
|
|
20
|
+
type JobStatusResponse = {
|
|
21
|
+
job_id: string;
|
|
22
|
+
status: JobStatus;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Result from submitAndPoll - discriminated union for success/failure.
|
|
26
|
+
*/
|
|
27
|
+
type QueueJobResult = {
|
|
28
|
+
status: "completed";
|
|
29
|
+
data: Blob;
|
|
30
|
+
} | {
|
|
31
|
+
status: "failed";
|
|
32
|
+
error: string;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Queue-specific inputs extending ProcessInputs.
|
|
36
|
+
* Re-exports ProcessInputs fields with queue-specific documentation.
|
|
37
|
+
*/
|
|
38
|
+
interface QueueInputs extends ProcessInputs {
|
|
39
|
+
/**
|
|
40
|
+
* The data to use for generation (for image-to-image and video-to-video).
|
|
41
|
+
*/
|
|
42
|
+
data?: FileInput;
|
|
43
|
+
/**
|
|
44
|
+
* The start frame image (for first-last-frame models).
|
|
45
|
+
*/
|
|
46
|
+
start?: FileInput;
|
|
47
|
+
/**
|
|
48
|
+
* The end frame image (for first-last-frame models).
|
|
49
|
+
*/
|
|
50
|
+
end?: FileInput;
|
|
51
|
+
}
|
|
52
|
+
type ModelSpecificQueueInputs<T extends ModelDefinition> = QueueInputs & ModelSpecificInputs<T>;
|
|
53
|
+
type PickDocumentedInputs<T extends ModelDefinition> = Pick<ModelSpecificQueueInputs<T>, keyof ModelSpecificQueueInputs<T> & keyof InferModelInputs<T>>;
|
|
54
|
+
type MergeDocumentedInputs<T extends ModelDefinition> = PickDocumentedInputs<T> & InferModelInputs<T>;
|
|
55
|
+
/**
|
|
56
|
+
* Options for queue.submit() - submit a job for async processing.
|
|
57
|
+
*/
|
|
58
|
+
type QueueSubmitOptions<T extends ModelDefinition = ModelDefinition> = {
|
|
59
|
+
/**
|
|
60
|
+
* The model definition to use.
|
|
61
|
+
*/
|
|
62
|
+
model: T;
|
|
63
|
+
/**
|
|
64
|
+
* Optional `AbortSignal` for canceling the request.
|
|
65
|
+
*/
|
|
66
|
+
signal?: AbortSignal;
|
|
67
|
+
} & MergeDocumentedInputs<T>;
|
|
68
|
+
/**
|
|
69
|
+
* Options for queue.submitAndPoll() - submit and wait for completion.
|
|
70
|
+
*/
|
|
71
|
+
type QueueSubmitAndPollOptions<T extends ModelDefinition = ModelDefinition> = QueueSubmitOptions<T> & {
|
|
72
|
+
/**
|
|
73
|
+
* Callback invoked when job status changes during polling.
|
|
74
|
+
* Receives the full job status response object.
|
|
75
|
+
*/
|
|
76
|
+
onStatusChange?: (job: JobStatusResponse) => void;
|
|
77
|
+
};
|
|
78
|
+
//#endregion
|
|
79
|
+
export { JobStatus, JobStatusResponse, JobSubmitResponse, QueueJobResult, QueueSubmitAndPollOptions, QueueSubmitOptions };
|
|
@@ -15,6 +15,7 @@ declare const realTimeClientConnectOptionsSchema: z.ZodObject<{
|
|
|
15
15
|
model: z.ZodObject<{
|
|
16
16
|
name: z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodLiteral<"mirage">, z.ZodLiteral<"mirage_v2">, z.ZodLiteral<"lucy_v2v_720p_rt">]>, z.ZodUnion<readonly [z.ZodLiteral<"lucy-dev-i2v">, z.ZodLiteral<"lucy-fast-v2v">, z.ZodLiteral<"lucy-pro-t2v">, z.ZodLiteral<"lucy-pro-i2v">, z.ZodLiteral<"lucy-pro-v2v">, z.ZodLiteral<"lucy-pro-flf2v">, z.ZodLiteral<"lucy-motion">]>, z.ZodUnion<readonly [z.ZodLiteral<"lucy-pro-t2i">, z.ZodLiteral<"lucy-pro-i2i">]>]>;
|
|
17
17
|
urlPath: z.ZodString;
|
|
18
|
+
queueUrlPath: z.ZodOptional<z.ZodString>;
|
|
18
19
|
fps: z.ZodNumber;
|
|
19
20
|
width: z.ZodNumber;
|
|
20
21
|
height: z.ZodNumber;
|
|
@@ -39,12 +40,12 @@ type RealTimeClient = {
|
|
|
39
40
|
enhance
|
|
40
41
|
}?: {
|
|
41
42
|
enhance?: boolean;
|
|
42
|
-
}) => void
|
|
43
|
+
}) => Promise<void>;
|
|
43
44
|
isConnected: () => boolean;
|
|
44
45
|
getConnectionState: () => "connected" | "connecting" | "disconnected";
|
|
45
46
|
disconnect: () => void;
|
|
46
|
-
on: (event:
|
|
47
|
-
off: (event:
|
|
47
|
+
on: <K extends keyof Events>(event: K, listener: (data: Events[K]) => void) => void;
|
|
48
|
+
off: <K extends keyof Events>(event: K, listener: (data: Events[K]) => void) => void;
|
|
48
49
|
sessionId: string;
|
|
49
50
|
};
|
|
50
51
|
//#endregion
|
package/dist/realtime/methods.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
3
|
//#region src/realtime/methods.ts
|
|
4
|
+
const PROMPT_TIMEOUT_MS = 15 * 1e3;
|
|
4
5
|
const realtimeMethods = (webrtcManager) => {
|
|
5
|
-
const setPrompt = (prompt, { enhance } = {}) => {
|
|
6
|
+
const setPrompt = async (prompt, { enhance } = {}) => {
|
|
6
7
|
const parsedInput = z.object({
|
|
7
8
|
prompt: z.string().min(1),
|
|
8
9
|
enhance: z.boolean().optional().default(true)
|
|
@@ -11,11 +12,30 @@ const realtimeMethods = (webrtcManager) => {
|
|
|
11
12
|
enhance
|
|
12
13
|
});
|
|
13
14
|
if (!parsedInput.success) throw parsedInput.error;
|
|
14
|
-
webrtcManager.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
const emitter = webrtcManager.getWebsocketMessageEmitter();
|
|
16
|
+
let promptAckListener;
|
|
17
|
+
let timeoutId;
|
|
18
|
+
try {
|
|
19
|
+
const ackPromise = new Promise((resolve, reject) => {
|
|
20
|
+
promptAckListener = (promptAckMessage) => {
|
|
21
|
+
if (promptAckMessage.prompt === parsedInput.data.prompt) if (promptAckMessage.success) resolve();
|
|
22
|
+
else reject(promptAckMessage.error);
|
|
23
|
+
};
|
|
24
|
+
emitter.on("promptAck", promptAckListener);
|
|
25
|
+
});
|
|
26
|
+
webrtcManager.sendMessage({
|
|
27
|
+
type: "prompt",
|
|
28
|
+
prompt: parsedInput.data.prompt,
|
|
29
|
+
enhance_prompt: parsedInput.data.enhance
|
|
30
|
+
});
|
|
31
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
32
|
+
timeoutId = setTimeout(() => reject(/* @__PURE__ */ new Error("Prompt timed out")), PROMPT_TIMEOUT_MS);
|
|
33
|
+
});
|
|
34
|
+
await Promise.race([ackPromise, timeoutPromise]);
|
|
35
|
+
} finally {
|
|
36
|
+
if (promptAckListener) emitter.off("promptAck", promptAckListener);
|
|
37
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
38
|
+
}
|
|
19
39
|
};
|
|
20
40
|
return { setPrompt };
|
|
21
41
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buildUserAgent } from "../utils/user-agent.js";
|
|
2
|
+
import mitt from "mitt";
|
|
2
3
|
|
|
3
4
|
//#region src/realtime/webrtc-connection.ts
|
|
4
5
|
const ICE_SERVERS = [{ urls: "stun:stun.l.google.com:19302" }];
|
|
@@ -8,6 +9,7 @@ var WebRTCConnection = class {
|
|
|
8
9
|
localStream = null;
|
|
9
10
|
connectionReject = null;
|
|
10
11
|
state = "disconnected";
|
|
12
|
+
websocketMessagesEmitter = mitt();
|
|
11
13
|
constructor(callbacks = {}) {
|
|
12
14
|
this.callbacks = callbacks;
|
|
13
15
|
}
|
|
@@ -105,6 +107,9 @@ var WebRTCConnection = class {
|
|
|
105
107
|
if (turnConfig) await this.setupNewPeerConnection(turnConfig);
|
|
106
108
|
break;
|
|
107
109
|
}
|
|
110
|
+
case "prompt_ack":
|
|
111
|
+
this.websocketMessagesEmitter.emit("promptAck", msg);
|
|
112
|
+
break;
|
|
108
113
|
}
|
|
109
114
|
} catch (error) {
|
|
110
115
|
console.error("[WebRTC] Error:", error);
|
package/dist/shared/model.d.ts
CHANGED
package/dist/shared/model.js
CHANGED
|
@@ -116,6 +116,7 @@ const modelInputSchemas = {
|
|
|
116
116
|
const modelDefinitionSchema = z.object({
|
|
117
117
|
name: modelSchema,
|
|
118
118
|
urlPath: z.string(),
|
|
119
|
+
queueUrlPath: z.string().optional(),
|
|
119
120
|
fps: z.number().min(1),
|
|
120
121
|
width: z.number().min(1),
|
|
121
122
|
height: z.number().min(1),
|
|
@@ -151,6 +152,7 @@ const _models = {
|
|
|
151
152
|
image: {
|
|
152
153
|
"lucy-pro-t2i": {
|
|
153
154
|
urlPath: "/v1/generate/lucy-pro-t2i",
|
|
155
|
+
queueUrlPath: "/v1/jobs/lucy-pro-t2i",
|
|
154
156
|
name: "lucy-pro-t2i",
|
|
155
157
|
fps: 25,
|
|
156
158
|
width: 1280,
|
|
@@ -159,6 +161,7 @@ const _models = {
|
|
|
159
161
|
},
|
|
160
162
|
"lucy-pro-i2i": {
|
|
161
163
|
urlPath: "/v1/generate/lucy-pro-i2i",
|
|
164
|
+
queueUrlPath: "/v1/jobs/lucy-pro-i2i",
|
|
162
165
|
name: "lucy-pro-i2i",
|
|
163
166
|
fps: 25,
|
|
164
167
|
width: 1280,
|
|
@@ -169,6 +172,7 @@ const _models = {
|
|
|
169
172
|
video: {
|
|
170
173
|
"lucy-dev-i2v": {
|
|
171
174
|
urlPath: "/v1/generate/lucy-dev-i2v",
|
|
175
|
+
queueUrlPath: "/v1/jobs/lucy-dev-i2v",
|
|
172
176
|
name: "lucy-dev-i2v",
|
|
173
177
|
fps: 25,
|
|
174
178
|
width: 1280,
|
|
@@ -177,6 +181,7 @@ const _models = {
|
|
|
177
181
|
},
|
|
178
182
|
"lucy-fast-v2v": {
|
|
179
183
|
urlPath: "/v1/generate/lucy-fast-v2v",
|
|
184
|
+
queueUrlPath: "/v1/jobs/lucy-fast-v2v",
|
|
180
185
|
name: "lucy-fast-v2v",
|
|
181
186
|
fps: 25,
|
|
182
187
|
width: 1280,
|
|
@@ -185,6 +190,7 @@ const _models = {
|
|
|
185
190
|
},
|
|
186
191
|
"lucy-pro-t2v": {
|
|
187
192
|
urlPath: "/v1/generate/lucy-pro-t2v",
|
|
193
|
+
queueUrlPath: "/v1/jobs/lucy-pro-t2v",
|
|
188
194
|
name: "lucy-pro-t2v",
|
|
189
195
|
fps: 25,
|
|
190
196
|
width: 1280,
|
|
@@ -193,6 +199,7 @@ const _models = {
|
|
|
193
199
|
},
|
|
194
200
|
"lucy-pro-i2v": {
|
|
195
201
|
urlPath: "/v1/generate/lucy-pro-i2v",
|
|
202
|
+
queueUrlPath: "/v1/jobs/lucy-pro-i2v",
|
|
196
203
|
name: "lucy-pro-i2v",
|
|
197
204
|
fps: 25,
|
|
198
205
|
width: 1280,
|
|
@@ -201,6 +208,7 @@ const _models = {
|
|
|
201
208
|
},
|
|
202
209
|
"lucy-pro-v2v": {
|
|
203
210
|
urlPath: "/v1/generate/lucy-pro-v2v",
|
|
211
|
+
queueUrlPath: "/v1/jobs/lucy-pro-v2v",
|
|
204
212
|
name: "lucy-pro-v2v",
|
|
205
213
|
fps: 25,
|
|
206
214
|
width: 1280,
|
|
@@ -209,6 +217,7 @@ const _models = {
|
|
|
209
217
|
},
|
|
210
218
|
"lucy-pro-flf2v": {
|
|
211
219
|
urlPath: "/v1/generate/lucy-pro-flf2v",
|
|
220
|
+
queueUrlPath: "/v1/jobs/lucy-pro-flf2v",
|
|
212
221
|
name: "lucy-pro-flf2v",
|
|
213
222
|
fps: 25,
|
|
214
223
|
width: 1280,
|
|
@@ -217,6 +226,7 @@ const _models = {
|
|
|
217
226
|
},
|
|
218
227
|
"lucy-motion": {
|
|
219
228
|
urlPath: "/v1/generate/lucy-motion",
|
|
229
|
+
queueUrlPath: "/v1/jobs/lucy-motion",
|
|
220
230
|
name: "lucy-motion",
|
|
221
231
|
fps: 25,
|
|
222
232
|
width: 1280,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createInvalidInputError } from "../utils/errors.js";
|
|
2
|
+
import { buildUserAgent } from "../utils/user-agent.js";
|
|
3
|
+
|
|
4
|
+
//#region src/shared/request.ts
|
|
5
|
+
/**
|
|
6
|
+
* Convert various file input types to a Blob.
|
|
7
|
+
*/
|
|
8
|
+
async function fileInputToBlob(input) {
|
|
9
|
+
if (input instanceof Blob || input instanceof File) return input;
|
|
10
|
+
if (input instanceof ReadableStream) return new Response(input).blob();
|
|
11
|
+
if (typeof input === "string" || input instanceof URL) {
|
|
12
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
13
|
+
if (!url.startsWith("http://") && !url.startsWith("https://")) throw createInvalidInputError("URL must start with http:// or https://");
|
|
14
|
+
const response = await fetch(url);
|
|
15
|
+
if (!response.ok) throw createInvalidInputError(`Failed to fetch file from URL: ${response.statusText}`);
|
|
16
|
+
return response.blob();
|
|
17
|
+
}
|
|
18
|
+
throw createInvalidInputError("Invalid file input type");
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build common headers for API requests.
|
|
22
|
+
*/
|
|
23
|
+
function buildAuthHeaders(apiKey, integration) {
|
|
24
|
+
return {
|
|
25
|
+
"X-API-KEY": apiKey,
|
|
26
|
+
"User-Agent": buildUserAgent(integration)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build FormData from inputs object.
|
|
31
|
+
*/
|
|
32
|
+
function buildFormData(inputs) {
|
|
33
|
+
const formData = new FormData();
|
|
34
|
+
for (const [key, value] of Object.entries(inputs)) if (value !== void 0 && value !== null) if (value instanceof Blob) formData.append(key, value);
|
|
35
|
+
else if (typeof value === "object" && value !== null) formData.append(key, JSON.stringify(value));
|
|
36
|
+
else formData.append(key, String(value));
|
|
37
|
+
return formData;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
export { buildAuthHeaders, buildFormData, fileInputToBlob };
|
package/dist/utils/errors.d.ts
CHANGED
|
@@ -13,6 +13,10 @@ declare const ERROR_CODES: {
|
|
|
13
13
|
readonly INVALID_INPUT: "INVALID_INPUT";
|
|
14
14
|
readonly INVALID_OPTIONS: "INVALID_OPTIONS";
|
|
15
15
|
readonly MODEL_NOT_FOUND: "MODEL_NOT_FOUND";
|
|
16
|
+
readonly QUEUE_SUBMIT_ERROR: "QUEUE_SUBMIT_ERROR";
|
|
17
|
+
readonly QUEUE_STATUS_ERROR: "QUEUE_STATUS_ERROR";
|
|
18
|
+
readonly QUEUE_RESULT_ERROR: "QUEUE_RESULT_ERROR";
|
|
19
|
+
readonly JOB_NOT_COMPLETED: "JOB_NOT_COMPLETED";
|
|
16
20
|
};
|
|
17
21
|
//#endregion
|
|
18
22
|
export { DecartSDKError, ERROR_CODES };
|
package/dist/utils/errors.js
CHANGED
|
@@ -6,7 +6,11 @@ const ERROR_CODES = {
|
|
|
6
6
|
PROCESSING_ERROR: "PROCESSING_ERROR",
|
|
7
7
|
INVALID_INPUT: "INVALID_INPUT",
|
|
8
8
|
INVALID_OPTIONS: "INVALID_OPTIONS",
|
|
9
|
-
MODEL_NOT_FOUND: "MODEL_NOT_FOUND"
|
|
9
|
+
MODEL_NOT_FOUND: "MODEL_NOT_FOUND",
|
|
10
|
+
QUEUE_SUBMIT_ERROR: "QUEUE_SUBMIT_ERROR",
|
|
11
|
+
QUEUE_STATUS_ERROR: "QUEUE_STATUS_ERROR",
|
|
12
|
+
QUEUE_RESULT_ERROR: "QUEUE_RESULT_ERROR",
|
|
13
|
+
JOB_NOT_COMPLETED: "JOB_NOT_COMPLETED"
|
|
10
14
|
};
|
|
11
15
|
function createSDKError(code, message, data, cause) {
|
|
12
16
|
return {
|
|
@@ -31,6 +35,15 @@ function createInvalidInputError(message) {
|
|
|
31
35
|
function createModelNotFoundError(model) {
|
|
32
36
|
return createSDKError(ERROR_CODES.MODEL_NOT_FOUND, `Model ${model} not found`);
|
|
33
37
|
}
|
|
38
|
+
function createQueueSubmitError(message, status) {
|
|
39
|
+
return createSDKError(ERROR_CODES.QUEUE_SUBMIT_ERROR, message, { status });
|
|
40
|
+
}
|
|
41
|
+
function createQueueStatusError(message, status) {
|
|
42
|
+
return createSDKError(ERROR_CODES.QUEUE_STATUS_ERROR, message, { status });
|
|
43
|
+
}
|
|
44
|
+
function createQueueResultError(message, status) {
|
|
45
|
+
return createSDKError(ERROR_CODES.QUEUE_RESULT_ERROR, message, { status });
|
|
46
|
+
}
|
|
34
47
|
|
|
35
48
|
//#endregion
|
|
36
|
-
export { ERROR_CODES, createInvalidApiKeyError, createInvalidBaseUrlError, createInvalidInputError, createModelNotFoundError, createSDKError, createWebrtcError };
|
|
49
|
+
export { ERROR_CODES, createInvalidApiKeyError, createInvalidBaseUrlError, createInvalidInputError, createModelNotFoundError, createQueueResultError, createQueueStatusError, createQueueSubmitError, createSDKError, createWebrtcError };
|
package/dist/version.js
CHANGED