@decartai/sdk 0.1.0 → 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.
@@ -0,0 +1,33 @@
1
+ import { FileReference, FileUploadInput } from "./types.js";
2
+
3
+ //#region src/files/client.d.ts
4
+
5
+ type UploadFileOptions = {
6
+ signal?: AbortSignal;
7
+ /**
8
+ * Expiration:
9
+ * - omit → platform default (24 h)
10
+ * - a positive integer → TTL in seconds (60 .. 2_592_000)
11
+ * - `"persistent"` → never expires
12
+ */
13
+ ttlSeconds?: number | "persistent";
14
+ };
15
+ type FilesClient = {
16
+ /**
17
+ * Upload a file once and get a reusable reference. Pass `ref.id` to
18
+ * realtime `set({ image })` to reuse the same asset across generations
19
+ * without re-uploading.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const ref = await client.files.upload(blob);
24
+ * await rt.set({ image: ref.id, prompt: "make it cinematic" });
25
+ * await rt.set({ image: ref.id, prompt: "now in noir" }); // reused, no re-upload
26
+ * ```
27
+ */
28
+ upload: (file: FileUploadInput, options?: UploadFileOptions) => Promise<FileReference>;
29
+ get: (fileId: string) => Promise<FileReference>;
30
+ delete: (fileId: string) => Promise<void>;
31
+ };
32
+ //#endregion
33
+ export { FilesClient, UploadFileOptions };
@@ -0,0 +1,65 @@
1
+ import { createInvalidInputError, createSDKError } from "../utils/errors.js";
2
+ import { buildAuthHeaders } from "../shared/request.js";
3
+ import { z } from "zod";
4
+ //#region src/files/client.ts
5
+ const MAX_TTL_SECONDS = 720 * 60 * 60;
6
+ const ttlSecondsSchema = z.union([z.number().int().min(60).max(MAX_TTL_SECONDS), z.literal("persistent")]);
7
+ const createFilesClient = (opts) => {
8
+ const { baseUrl, apiKey, integration } = opts;
9
+ const upload = async (file, options) => {
10
+ if (options?.ttlSeconds !== void 0) {
11
+ if (!ttlSecondsSchema.safeParse(options.ttlSeconds).success) throw createInvalidInputError(`ttlSeconds must be an integer in [60, ${MAX_TTL_SECONDS}] or the literal "persistent"`);
12
+ }
13
+ const formData = new FormData();
14
+ formData.append("file", file);
15
+ if (options?.ttlSeconds !== void 0) formData.append("ttl_seconds", String(options.ttlSeconds));
16
+ const response = await fetch(`${baseUrl}/v1/files`, {
17
+ method: "POST",
18
+ headers: buildAuthHeaders({
19
+ apiKey,
20
+ integration
21
+ }),
22
+ body: formData,
23
+ signal: options?.signal
24
+ });
25
+ if (!response.ok) {
26
+ const errorText = await response.text().catch(() => "Unknown error");
27
+ throw createSDKError("FILES_UPLOAD_ERROR", `Failed to upload file: ${response.status} - ${errorText}`, { status: response.status });
28
+ }
29
+ return response.json();
30
+ };
31
+ const get = async (fileId) => {
32
+ const response = await fetch(`${baseUrl}/v1/files/${encodeURIComponent(fileId)}`, {
33
+ method: "GET",
34
+ headers: buildAuthHeaders({
35
+ apiKey,
36
+ integration
37
+ })
38
+ });
39
+ if (!response.ok) {
40
+ const errorText = await response.text().catch(() => "Unknown error");
41
+ throw createSDKError("FILES_GET_ERROR", `Failed to get file: ${response.status} - ${errorText}`, { status: response.status });
42
+ }
43
+ return response.json();
44
+ };
45
+ const deleteFile = async (fileId) => {
46
+ const response = await fetch(`${baseUrl}/v1/files/${encodeURIComponent(fileId)}`, {
47
+ method: "DELETE",
48
+ headers: buildAuthHeaders({
49
+ apiKey,
50
+ integration
51
+ })
52
+ });
53
+ if (!response.ok) {
54
+ const errorText = await response.text().catch(() => "Unknown error");
55
+ throw createSDKError("FILES_DELETE_ERROR", `Failed to delete file: ${response.status} - ${errorText}`, { status: response.status });
56
+ }
57
+ };
58
+ return {
59
+ upload,
60
+ get,
61
+ delete: deleteFile
62
+ };
63
+ };
64
+ //#endregion
65
+ export { createFilesClient };
@@ -0,0 +1,22 @@
1
+ import { ReactNativeFile } from "../process/types.js";
2
+
3
+ //#region src/files/types.d.ts
4
+
5
+ /**
6
+ * Metadata for a previously-uploaded file. Returned by `client.files.upload(...)`.
7
+ * Pass `ref.id` to `realtime.set({ image })` / `setImage(...)` to reuse it.
8
+ *
9
+ * Files expire after a server-configured TTL (default 24 h). `expires_at` is
10
+ * `null` when the upload was created with `persistent: true`.
11
+ */
12
+ interface FileReference {
13
+ id: string;
14
+ filename: string | null;
15
+ mime_type: string;
16
+ size_bytes: number;
17
+ created_at: string;
18
+ expires_at: string | null;
19
+ }
20
+ type FileUploadInput = File | Blob | ReactNativeFile;
21
+ //#endregion
22
+ export { FileReference, FileUploadInput };
@@ -0,0 +1,4 @@
1
+ /** True if `value` is a `"file_..."` reference id from `client.files.upload(...)`. */
2
+ const isFileRefId = (value) => typeof value === "string" && value.startsWith("file_");
3
+ //#endregion
4
+ export { isFileRefId };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { LogLevel, Logger, createConsoleLogger, noopLogger } from "./utils/logger.js";
2
2
  import { CanonicalModel, CustomModelDefinition, ImageModelDefinition, ImageModels, ListedModelDefinition, Model, ModelDefinition, ModelKind, RealTimeModels, VideoModelDefinition, VideoModels, isCanonicalModel, isImageModel, isModel, isRealtimeModel, isVideoModel, listModels, modelAliases, models, resolveCanonicalModelAlias, resolveModelAlias } from "./shared/model.js";
3
3
  import { FileInput, ProcessOptions, ReactNativeFile } from "./process/types.js";
4
+ import { FileReference, FileUploadInput } from "./files/types.js";
5
+ import { FilesClient, UploadFileOptions } from "./files/client.js";
4
6
  import { ProcessClient } from "./process/client.js";
5
7
  import { JobStatus, JobStatusResponse, JobSubmitResponse, QueueJobResult, QueueSubmitAndPollOptions, QueueSubmitOptions } from "./queue/types.js";
6
8
  import { QueueClient } from "./queue/client.js";
@@ -134,6 +136,17 @@ declare const createDecartClient: (options?: DecartClientOptions) => {
134
136
  * ```
135
137
  */
136
138
  tokens: TokensClient;
139
+ /**
140
+ * Upload files once and reuse them across generations.
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * const ref = await client.files.upload(blob);
145
+ * await rt.set({ image: ref.id, prompt: "make it cinematic" });
146
+ * await rt.set({ image: ref.id, prompt: "now in noir" });
147
+ * ```
148
+ */
149
+ files: FilesClient;
137
150
  };
138
151
  //#endregion
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 };
152
+ 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 FileReference, type FileUploadInput, type FilesClient, 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 UploadFileOptions, 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
@@ -1,4 +1,5 @@
1
1
  import { ERROR_CODES, createInvalidApiKeyError, createInvalidBaseUrlError } from "./utils/errors.js";
2
+ import { createFilesClient } from "./files/client.js";
2
3
  import { createProcessClient } from "./process/client.js";
3
4
  import { createQueueClient } from "./queue/client.js";
4
5
  import { isCanonicalModel, isImageModel, isModel, isRealtimeModel, isVideoModel, listModels, modelAliases, models, resolveCanonicalModelAlias, resolveModelAlias } from "./shared/model.js";
@@ -92,6 +93,11 @@ const createDecartClient = (options = {}) => {
92
93
  apiKey: apiKey || "",
93
94
  integration
94
95
  });
96
+ const files = createFilesClient({
97
+ baseUrl,
98
+ apiKey: apiKey || "",
99
+ integration
100
+ });
95
101
  return {
96
102
  realtime: {
97
103
  connect: realtimePublish.connect,
@@ -99,7 +105,8 @@ const createDecartClient = (options = {}) => {
99
105
  },
100
106
  process,
101
107
  queue,
102
- tokens
108
+ tokens,
109
+ files
103
110
  };
104
111
  };
105
112
  //#endregion
@@ -78,6 +78,12 @@ type RealTimeClient = {
78
78
  sessionId: string | null;
79
79
  subscribeToken: string | null;
80
80
  getSubscribeToken: () => string | null;
81
+ /**
82
+ * Set the reference image for the session.
83
+ * - `Blob`/`File`/data URL/http(s) URL/base64 string: bytes traverse the wire as base64.
84
+ * - `"file_..."` id (from `client.files.upload(...).id`): sent as a server-side reference.
85
+ * - `null`: clear the current image.
86
+ */
81
87
  setImage: (image: Blob | File | string | null, options?: ImageSetOptions) => Promise<void>;
82
88
  };
83
89
  //#endregion
@@ -1,4 +1,5 @@
1
1
  import { classifyWebrtcError } from "../utils/errors.js";
2
+ import { isFileRefId } from "../files/types.js";
2
3
  import { modelDefinitionSchema, resolveFpsNumber } from "../shared/model.js";
3
4
  import { modelStateSchema } from "../shared/types.js";
4
5
  import { createConsoleLogger } from "../utils/logger.js";
@@ -44,7 +45,8 @@ const createRealTimeClient = (opts) => {
44
45
  let session;
45
46
  let observability;
46
47
  try {
47
- const initialImage = initialState?.image ? await imageToBase64(initialState.image) : void 0;
48
+ const initialImageRef = isFileRefId(initialState?.image) ? initialState.image : void 0;
49
+ const initialImage = initialImageRef === void 0 && initialState?.image ? await imageToBase64(initialState.image) : void 0;
48
50
  const initialPrompt = initialState?.prompt ? {
49
51
  text: initialState.prompt.text,
50
52
  enhance: initialState.prompt.enhance
@@ -73,6 +75,7 @@ const createRealTimeClient = (opts) => {
73
75
  observability,
74
76
  localStream: inputStream,
75
77
  initialImage,
78
+ initialImageRef,
76
79
  initialPrompt,
77
80
  logger,
78
81
  videoCodec: safariCodec
@@ -121,9 +124,19 @@ const createRealTimeClient = (opts) => {
121
124
  },
122
125
  getSubscribeToken: () => subscribeToken,
123
126
  setImage: async (image, imgOptions) => {
124
- if (image === null) return activeSession.setImage(null, imgOptions);
127
+ if (isFileRefId(image)) return activeSession.setImage({
128
+ kind: "ref",
129
+ ref: image
130
+ }, imgOptions);
131
+ if (image === null) return activeSession.setImage({
132
+ kind: "data",
133
+ data: null
134
+ }, imgOptions);
125
135
  const base64 = await imageToBase64(image);
126
- return activeSession.setImage(base64, imgOptions);
136
+ return activeSession.setImage({
137
+ kind: "data",
138
+ data: base64
139
+ }, imgOptions);
127
140
  }
128
141
  };
129
142
  flush();
@@ -1,3 +1,4 @@
1
+ import { isFileRefId } from "../files/types.js";
1
2
  import { REALTIME_CONFIG } from "./config-realtime.js";
2
3
  import { z } from "zod";
3
4
  //#region src/realtime/methods.ts
@@ -20,12 +21,23 @@ const realtimeMethods = (session, imageToBase64) => {
20
21
  const parsed = setInputSchema.safeParse(input);
21
22
  if (!parsed.success) throw parsed.error;
22
23
  const { prompt, enhance, image } = parsed.data;
23
- const imageBase64 = image !== void 0 && image !== null ? await imageToBase64(image) : null;
24
- await session.setImage(imageBase64, {
24
+ const options = {
25
25
  prompt,
26
26
  enhance,
27
27
  timeout: REALTIME_CONFIG.methods.updateTimeoutMs
28
- });
28
+ };
29
+ if (isFileRefId(image)) {
30
+ await session.setImage({
31
+ kind: "ref",
32
+ ref: image
33
+ }, options);
34
+ return;
35
+ }
36
+ const imageBase64 = image !== void 0 && image !== null ? await imageToBase64(image) : null;
37
+ await session.setImage({
38
+ kind: "data",
39
+ data: imageBase64
40
+ }, options);
29
41
  };
30
42
  const setPrompt = async (prompt, { enhance } = {}) => {
31
43
  const parsed = setPromptInputSchema.safeParse({
@@ -79,10 +79,13 @@ var SignalingChannel = class {
79
79
  });
80
80
  if (!ack.success) throw new Error(ack.error ?? "Failed to send prompt");
81
81
  }
82
- async setImage(image, opts = {}) {
83
- const message = {
82
+ async setImage(payload, opts = {}) {
83
+ const message = payload.kind === "ref" ? {
84
84
  type: "set_image",
85
- image_data: image
85
+ image_ref: payload.ref
86
+ } : {
87
+ type: "set_image",
88
+ image_data: payload.data
86
89
  };
87
90
  if (opts.prompt !== void 0) message.prompt = opts.prompt;
88
91
  if (opts.enhance !== void 0) message.enhance_prompt = opts.enhance;
@@ -179,8 +182,21 @@ var SignalingChannel = class {
179
182
  }
180
183
  async sendInitialState(initialState) {
181
184
  if (!initialState) return;
185
+ if (initialState.imageRef !== void 0) {
186
+ await this.setImage({
187
+ kind: "ref",
188
+ ref: initialState.imageRef
189
+ }, {
190
+ prompt: initialState.prompt,
191
+ enhance: initialState.enhance
192
+ });
193
+ return;
194
+ }
182
195
  if (initialState.image !== void 0) {
183
- await this.setImage(initialState.image, {
196
+ await this.setImage({
197
+ kind: "data",
198
+ data: initialState.image
199
+ }, {
184
200
  prompt: initialState.prompt,
185
201
  enhance: initialState.enhance
186
202
  });
@@ -68,9 +68,9 @@ var StreamSession = class {
68
68
  this.assertConnected();
69
69
  return this.signaling.sendPrompt(text, opts);
70
70
  }
71
- async setImage(image, opts) {
71
+ async setImage(payload, opts) {
72
72
  this.assertConnected();
73
- return this.signaling.setImage(image, opts);
73
+ return this.signaling.setImage(payload, opts);
74
74
  }
75
75
  disconnect() {
76
76
  this.disposed = true;
@@ -141,6 +141,11 @@ var StreamSession = class {
141
141
  }
142
142
  }
143
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
+ };
144
149
  if (this.config.initialImage !== void 0) return {
145
150
  image: this.config.initialImage,
146
151
  prompt: this.config.initialPrompt?.text,
@@ -17,6 +17,9 @@ declare const ERROR_CODES: {
17
17
  readonly QUEUE_RESULT_ERROR: "QUEUE_RESULT_ERROR";
18
18
  readonly JOB_NOT_COMPLETED: "JOB_NOT_COMPLETED";
19
19
  readonly TOKEN_CREATE_ERROR: "TOKEN_CREATE_ERROR";
20
+ readonly FILES_UPLOAD_ERROR: "FILES_UPLOAD_ERROR";
21
+ readonly FILES_GET_ERROR: "FILES_GET_ERROR";
22
+ readonly FILES_DELETE_ERROR: "FILES_DELETE_ERROR";
20
23
  readonly WEBRTC_WEBSOCKET_ERROR: "WEBRTC_WEBSOCKET_ERROR";
21
24
  readonly WEBRTC_ICE_ERROR: "WEBRTC_ICE_ERROR";
22
25
  readonly WEBRTC_TIMEOUT_ERROR: "WEBRTC_TIMEOUT_ERROR";
@@ -11,6 +11,9 @@ const ERROR_CODES = {
11
11
  QUEUE_RESULT_ERROR: "QUEUE_RESULT_ERROR",
12
12
  JOB_NOT_COMPLETED: "JOB_NOT_COMPLETED",
13
13
  TOKEN_CREATE_ERROR: "TOKEN_CREATE_ERROR",
14
+ FILES_UPLOAD_ERROR: "FILES_UPLOAD_ERROR",
15
+ FILES_GET_ERROR: "FILES_GET_ERROR",
16
+ FILES_DELETE_ERROR: "FILES_DELETE_ERROR",
14
17
  WEBRTC_WEBSOCKET_ERROR: "WEBRTC_WEBSOCKET_ERROR",
15
18
  WEBRTC_ICE_ERROR: "WEBRTC_ICE_ERROR",
16
19
  WEBRTC_TIMEOUT_ERROR: "WEBRTC_TIMEOUT_ERROR",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decartai/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Decart's JavaScript SDK",
5
5
  "type": "module",
6
6
  "license": "MIT",