@sogni-ai/sogni-client 4.0.0-alpha.3 → 4.0.0-alpha.31

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 (76) hide show
  1. package/CHANGELOG.md +220 -0
  2. package/README.md +279 -28
  3. package/dist/Account/index.d.ts +18 -16
  4. package/dist/Account/index.js +31 -20
  5. package/dist/Account/index.js.map +1 -1
  6. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.d.ts +66 -0
  7. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.js +332 -0
  8. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.js.map +1 -0
  9. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.d.ts +28 -0
  10. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js +203 -0
  11. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js.map +1 -0
  12. package/dist/ApiClient/WebSocketClient/events.d.ts +11 -0
  13. package/dist/ApiClient/WebSocketClient/index.d.ts +2 -2
  14. package/dist/ApiClient/WebSocketClient/index.js +13 -3
  15. package/dist/ApiClient/WebSocketClient/index.js.map +1 -1
  16. package/dist/ApiClient/WebSocketClient/types.d.ts +13 -0
  17. package/dist/ApiClient/index.d.ts +4 -4
  18. package/dist/ApiClient/index.js +23 -4
  19. package/dist/ApiClient/index.js.map +1 -1
  20. package/dist/Projects/Job.d.ts +44 -4
  21. package/dist/Projects/Job.js +83 -16
  22. package/dist/Projects/Job.js.map +1 -1
  23. package/dist/Projects/Project.d.ts +18 -0
  24. package/dist/Projects/Project.js +34 -6
  25. package/dist/Projects/Project.js.map +1 -1
  26. package/dist/Projects/createJobRequestMessage.js +109 -15
  27. package/dist/Projects/createJobRequestMessage.js.map +1 -1
  28. package/dist/Projects/index.d.ts +110 -11
  29. package/dist/Projects/index.js +423 -42
  30. package/dist/Projects/index.js.map +1 -1
  31. package/dist/Projects/types/EstimationResponse.d.ts +2 -0
  32. package/dist/Projects/types/SamplerParams.d.ts +13 -0
  33. package/dist/Projects/types/SamplerParams.js +26 -0
  34. package/dist/Projects/types/SamplerParams.js.map +1 -0
  35. package/dist/Projects/types/SchedulerParams.d.ts +14 -0
  36. package/dist/Projects/types/SchedulerParams.js +24 -0
  37. package/dist/Projects/types/SchedulerParams.js.map +1 -0
  38. package/dist/Projects/types/events.d.ts +5 -1
  39. package/dist/Projects/types/index.d.ts +150 -39
  40. package/dist/Projects/types/index.js +13 -0
  41. package/dist/Projects/types/index.js.map +1 -1
  42. package/dist/Projects/utils.d.ts +19 -1
  43. package/dist/Projects/utils.js +68 -0
  44. package/dist/Projects/utils.js.map +1 -1
  45. package/dist/index.d.ts +12 -4
  46. package/dist/index.js +12 -4
  47. package/dist/index.js.map +1 -1
  48. package/dist/lib/AuthManager/TokenAuthManager.js +0 -2
  49. package/dist/lib/AuthManager/TokenAuthManager.js.map +1 -1
  50. package/dist/lib/DataEntity.js +4 -2
  51. package/dist/lib/DataEntity.js.map +1 -1
  52. package/dist/lib/validation.d.ts +7 -0
  53. package/dist/lib/validation.js +36 -0
  54. package/dist/lib/validation.js.map +1 -1
  55. package/package.json +4 -4
  56. package/src/Account/index.ts +30 -19
  57. package/src/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.ts +426 -0
  58. package/src/ApiClient/WebSocketClient/BrowserWebSocketClient/index.ts +237 -0
  59. package/src/ApiClient/WebSocketClient/events.ts +13 -0
  60. package/src/ApiClient/WebSocketClient/index.ts +15 -5
  61. package/src/ApiClient/WebSocketClient/types.ts +16 -0
  62. package/src/ApiClient/index.ts +30 -8
  63. package/src/Projects/Job.ts +97 -16
  64. package/src/Projects/Project.ts +42 -9
  65. package/src/Projects/createJobRequestMessage.ts +155 -36
  66. package/src/Projects/index.ts +447 -46
  67. package/src/Projects/types/EstimationResponse.ts +2 -0
  68. package/src/Projects/types/SamplerParams.ts +24 -0
  69. package/src/Projects/types/SchedulerParams.ts +22 -0
  70. package/src/Projects/types/events.ts +6 -0
  71. package/src/Projects/types/index.ts +181 -47
  72. package/src/Projects/utils.ts +66 -1
  73. package/src/index.ts +38 -11
  74. package/src/lib/AuthManager/TokenAuthManager.ts +0 -2
  75. package/src/lib/DataEntity.ts +4 -2
  76. package/src/lib/validation.ts +41 -0
@@ -20,9 +20,37 @@ const getUUID_1 = __importDefault(require("../lib/getUUID"));
20
20
  const Cache_1 = __importDefault(require("../lib/Cache"));
21
21
  const Job_1 = require("./Job");
22
22
  const utils_1 = require("./utils");
23
+ const validation_1 = require("../lib/validation");
23
24
  const sizePresetCache = new Cache_1.default(10 * 60 * 1000);
24
25
  const GARBAGE_COLLECT_TIMEOUT = 30000;
25
26
  const MODELS_REFRESH_INTERVAL = 1000 * 60 * 60 * 24; // 24 hours
27
+ /**
28
+ * Detect content type from a file object.
29
+ * For File objects in browser, uses the type property.
30
+ * Returns undefined if content type cannot be detected.
31
+ */
32
+ function getFileContentType(file) {
33
+ if (file instanceof Blob && 'type' in file && file.type) {
34
+ return file.type;
35
+ }
36
+ return undefined;
37
+ }
38
+ /**
39
+ * Convert file to a format compatible with fetch body.
40
+ * Converts Node.js Buffer to Blob for cross-platform compatibility.
41
+ */
42
+ function toFetchBody(file) {
43
+ // Node.js Buffer is not supported in browsers, so we can skip this conversion
44
+ if (typeof Buffer === 'undefined') {
45
+ return file;
46
+ }
47
+ if (Buffer.isBuffer(file)) {
48
+ // Copy Buffer data to a new ArrayBuffer to ensure type compatibility
49
+ const arrayBuffer = file.buffer.slice(file.byteOffset, file.byteOffset + file.byteLength);
50
+ return new Blob([arrayBuffer]);
51
+ }
52
+ return file;
53
+ }
26
54
  function mapErrorCodes(code) {
27
55
  switch (code) {
28
56
  case 'serverRestarting':
@@ -43,6 +71,20 @@ class ProjectsApi extends ApiGroup_1.default {
43
71
  get availableModels() {
44
72
  return this._availableModels;
45
73
  }
74
+ /**
75
+ * Check if a model produces video output using the cached models list.
76
+ * Uses the `media` property from the models API when available,
77
+ * falls back to model ID prefix check if models aren't loaded yet.
78
+ */
79
+ isVideoModelId(modelId) {
80
+ var _a;
81
+ const model = (_a = this._supportedModels.data) === null || _a === void 0 ? void 0 : _a.find((m) => m.id === modelId);
82
+ if (model) {
83
+ return model.media === 'video';
84
+ }
85
+ // Fallback to prefix check if models not loaded
86
+ return (0, utils_1.isVideoModel)(modelId);
87
+ }
46
88
  constructor(config) {
47
89
  super(config);
48
90
  this._availableModels = [];
@@ -56,6 +98,7 @@ class ProjectsApi extends ApiGroup_1.default {
56
98
  this.client.socket.on('swarmModels', this.handleSwarmModels.bind(this));
57
99
  this.client.socket.on('jobState', this.handleJobState.bind(this));
58
100
  this.client.socket.on('jobProgress', this.handleJobProgress.bind(this));
101
+ this.client.socket.on('jobETA', this.handleJobETA.bind(this));
59
102
  this.client.socket.on('jobError', this.handleJobError.bind(this));
60
103
  this.client.socket.on('jobResult', this.handleJobResult.bind(this));
61
104
  // Listen to the server disconnect event
@@ -64,6 +107,16 @@ class ProjectsApi extends ApiGroup_1.default {
64
107
  this.on('project', this.handleProjectEvent.bind(this));
65
108
  this.on('job', this.handleJobEvent.bind(this));
66
109
  }
110
+ /**
111
+ * Retrieves a list of projects created and tracked by this SogniClient instance.
112
+ *
113
+ * Note: When a project is finished, it will be removed from this list after 30 seconds
114
+ *
115
+ * @return {Array} A copy of the array containing the tracked projects.
116
+ */
117
+ get trackedProjects() {
118
+ return this.projects.slice(0);
119
+ }
67
120
  handleChangeNetwork() {
68
121
  this._availableModels = [];
69
122
  this.emit('availableModels', this._availableModels);
@@ -82,11 +135,12 @@ class ProjectsApi extends ApiGroup_1.default {
82
135
  return acc;
83
136
  }, {});
84
137
  this._availableModels = Object.entries(data).map(([id, workerCount]) => {
85
- var _a;
138
+ var _a, _b;
86
139
  return ({
87
140
  id,
88
141
  name: ((_a = modelIndex[id]) === null || _a === void 0 ? void 0 : _a.name) || id.replace(/-/g, ' '),
89
- workerCount
142
+ workerCount,
143
+ media: ((_b = modelIndex[id]) === null || _b === void 0 ? void 0 : _b.media) || 'image'
90
144
  });
91
145
  });
92
146
  this.emit('availableModels', this._availableModels);
@@ -154,6 +208,16 @@ class ProjectsApi extends ApiGroup_1.default {
154
208
  }
155
209
  });
156
210
  }
211
+ handleJobETA(data) {
212
+ return __awaiter(this, void 0, void 0, function* () {
213
+ this.emit('job', {
214
+ type: 'jobETA',
215
+ projectId: data.jobID,
216
+ jobId: data.imgID || '',
217
+ etaSeconds: data.etaSeconds
218
+ });
219
+ });
220
+ }
157
221
  handleJobResult(data) {
158
222
  return __awaiter(this, void 0, void 0, function* () {
159
223
  const project = this.projects.find((p) => p.id === data.jobID);
@@ -162,11 +226,22 @@ class ProjectsApi extends ApiGroup_1.default {
162
226
  // If NSFW filter is triggered, image will be only available for download if user explicitly
163
227
  // disabled the filter for this project
164
228
  if (passNSFWCheck && !data.userCanceled) {
165
- downloadUrl = yield this.downloadUrl({
166
- jobId: data.jobID,
167
- imageId: data.imgID,
168
- type: 'complete'
169
- });
229
+ // Use media endpoint for video models, image endpoint for image models
230
+ const isVideo = project && this.isVideoModelId(project.params.modelId);
231
+ if (isVideo) {
232
+ downloadUrl = yield this.mediaDownloadUrl({
233
+ jobId: data.jobID,
234
+ id: data.imgID,
235
+ type: 'complete'
236
+ });
237
+ }
238
+ else {
239
+ downloadUrl = yield this.downloadUrl({
240
+ jobId: data.jobID,
241
+ imageId: data.imgID,
242
+ type: 'complete'
243
+ });
244
+ }
170
245
  }
171
246
  this.emit('job', {
172
247
  type: 'completed',
@@ -238,7 +313,11 @@ class ProjectsApi extends ApiGroup_1.default {
238
313
  if (project.finished) {
239
314
  // Sync project data with the server and remove it from the list after some time
240
315
  project._syncToServer().catch((e) => {
241
- this.client.logger.error(e);
316
+ // 404 errors are expected when project is still initializing
317
+ // Only log non-404 errors to avoid confusing users
318
+ if (e.status !== 404) {
319
+ this.client.logger.error(e);
320
+ }
242
321
  });
243
322
  setTimeout(() => {
244
323
  this.projects = this.projects.filter((p) => !p.finished);
@@ -246,6 +325,7 @@ class ProjectsApi extends ApiGroup_1.default {
246
325
  }
247
326
  }
248
327
  handleJobEvent(event) {
328
+ var _a, _b, _c;
249
329
  let project = this.projects.find((p) => p.id === event.projectId);
250
330
  if (!project) {
251
331
  return;
@@ -257,7 +337,7 @@ class ProjectsApi extends ApiGroup_1.default {
257
337
  projectId: event.projectId,
258
338
  status: 'pending',
259
339
  step: 0,
260
- stepCount: project.params.steps
340
+ stepCount: (_a = project.params.steps) !== null && _a !== void 0 ? _a : 0
261
341
  });
262
342
  }
263
343
  switch (event.type) {
@@ -284,7 +364,7 @@ class ProjectsApi extends ApiGroup_1.default {
284
364
  case 'progress':
285
365
  job._update({
286
366
  status: 'processing',
287
- // Jus in case event comes out of order
367
+ // Just in case event comes out of order
288
368
  step: Math.max(event.step, job.step),
289
369
  stepCount: event.stepCount
290
370
  });
@@ -292,6 +372,26 @@ class ProjectsApi extends ApiGroup_1.default {
292
372
  project._update({ status: 'processing' });
293
373
  }
294
374
  break;
375
+ case 'jobETA': {
376
+ const newEta = new Date(Date.now() + event.etaSeconds * 1000);
377
+ if (((_b = job.eta) === null || _b === void 0 ? void 0 : _b.getTime()) !== (newEta === null || newEta === void 0 ? void 0 : newEta.getTime())) {
378
+ job._update({ eta: newEta });
379
+ const maxEta = project.jobs.reduce((max, j) => { var _a; return Math.max(max, ((_a = j.eta) === null || _a === void 0 ? void 0 : _a.getTime()) || 0); }, 0);
380
+ const projectETA = maxEta ? new Date(maxEta) : undefined;
381
+ if (((_c = project.eta) === null || _c === void 0 ? void 0 : _c.getTime()) !== (projectETA === null || projectETA === void 0 ? void 0 : projectETA.getTime())) {
382
+ project._update({ eta: projectETA });
383
+ }
384
+ }
385
+ else {
386
+ // ETA updates keep the project alive (refreshes lastUpdated) and store the ETA value.
387
+ // This is critical for long-running jobs like video generation that can take several
388
+ // minutes and may not send frequent progress updates.
389
+ // We call _keepAlive() directly to ensure lastUpdated is refreshed even if the ETA
390
+ // value hasn't changed (job._update only emits 'updated' when values actually change).
391
+ project._keepAlive();
392
+ }
393
+ break;
394
+ }
295
395
  case 'preview':
296
396
  job._update({ previewUrl: event.url });
297
397
  break;
@@ -308,6 +408,17 @@ class ProjectsApi extends ApiGroup_1.default {
308
408
  }
309
409
  case 'error':
310
410
  job._update({ status: 'failed', error: event.error });
411
+ // Check if project should also fail when a job fails
412
+ // For video jobs (single image) or when all jobs have failed, propagate to project
413
+ const allJobsStarted = project.jobs.length >= project.params.numberOfMedia;
414
+ const allJobsFailed = allJobsStarted && project.jobs.every((j) => j.status === 'failed');
415
+ const isSingleJobProject = project.params.numberOfMedia === 1;
416
+ if (isSingleJobProject || allJobsFailed) {
417
+ project._update({
418
+ status: 'failed',
419
+ error: event.error
420
+ });
421
+ }
311
422
  break;
312
423
  }
313
424
  }
@@ -328,41 +439,67 @@ class ProjectsApi extends ApiGroup_1.default {
328
439
  return Promise.resolve(this._availableModels);
329
440
  }
330
441
  return new Promise((resolve, reject) => {
442
+ let settled = false;
331
443
  const timeoutId = setTimeout(() => {
332
- reject(new Error('Timeout waiting for models'));
444
+ if (!settled) {
445
+ settled = true;
446
+ this.off('availableModels', handler);
447
+ reject(new Error('Timeout waiting for models'));
448
+ }
333
449
  }, timeout);
334
- this.once('availableModels', (models) => {
335
- clearTimeout(timeoutId);
336
- if (models.length) {
450
+ const handler = (models) => {
451
+ // Only resolve when we get a non-empty models list
452
+ // Empty arrays may be emitted during disconnects/reconnects
453
+ if (models.length && !settled) {
454
+ settled = true;
455
+ clearTimeout(timeoutId);
456
+ this.off('availableModels', handler);
337
457
  resolve(models);
338
458
  }
339
- else {
340
- reject(new Error('No models available'));
341
- }
342
- });
459
+ };
460
+ this.on('availableModels', handler);
343
461
  });
344
462
  }
345
463
  /**
346
464
  * Send new project request to the network. Returns project instance which can be used to track
347
- * progress and get resulting images.
465
+ * progress and get resulting images or videos.
348
466
  * @param data
349
467
  */
350
468
  create(data) {
351
469
  return __awaiter(this, void 0, void 0, function* () {
352
- var _a, _b;
353
470
  const project = new Project_1.default(Object.assign({}, data), { api: this, logger: this.client.logger });
471
+ const request = (0, createJobRequestMessage_1.default)(project.id, data);
472
+ switch (data.type) {
473
+ case 'image':
474
+ yield this._processImageAssets(project, data);
475
+ break;
476
+ case 'video':
477
+ yield this._processVideoAssets(project, data);
478
+ break;
479
+ }
480
+ yield this.client.socket.send('jobRequest', request);
481
+ this.projects.push(project);
482
+ return project;
483
+ });
484
+ }
485
+ _processImageAssets(project, data) {
486
+ return __awaiter(this, void 0, void 0, function* () {
487
+ var _a, _b;
488
+ //Guide image
354
489
  if (data.startingImage && data.startingImage !== true) {
355
490
  yield this.uploadGuideImage(project.id, data.startingImage);
356
491
  }
492
+ // ControlNet image
357
493
  if (((_a = data.controlNet) === null || _a === void 0 ? void 0 : _a.image) && data.controlNet.image !== true) {
358
494
  yield this.uploadCNImage(project.id, data.controlNet.image);
359
495
  }
496
+ // Context images (Flux.2 Dev, Qwen Image Edit Plus support up to 3; Flux Kontext supports up to 2)
360
497
  if ((_b = data.contextImages) === null || _b === void 0 ? void 0 : _b.length) {
361
- if (data.contextImages.length > 2) {
498
+ if (data.contextImages.length > 3) {
362
499
  throw new ApiClient_1.ApiError(500, {
363
500
  status: 'error',
364
501
  errorCode: 0,
365
- message: `Up to 2 context images are supported`
502
+ message: `Up to 3 context images are supported`
366
503
  });
367
504
  }
368
505
  yield Promise.all(data.contextImages.map((image, index) => {
@@ -371,10 +508,22 @@ class ProjectsApi extends ApiGroup_1.default {
371
508
  }
372
509
  }));
373
510
  }
374
- const request = (0, createJobRequestMessage_1.default)(project.id, data);
375
- yield this.client.socket.send('jobRequest', request);
376
- this.projects.push(project);
377
- return project;
511
+ });
512
+ }
513
+ _processVideoAssets(project, data) {
514
+ return __awaiter(this, void 0, void 0, function* () {
515
+ if ((data === null || data === void 0 ? void 0 : data.referenceImage) && data.referenceImage !== true) {
516
+ yield this.uploadReferenceImage(project.id, data.referenceImage);
517
+ }
518
+ if ((data === null || data === void 0 ? void 0 : data.referenceImageEnd) && data.referenceImageEnd !== true) {
519
+ yield this.uploadReferenceImageEnd(project.id, data.referenceImageEnd);
520
+ }
521
+ if ((data === null || data === void 0 ? void 0 : data.referenceAudio) && data.referenceAudio !== true) {
522
+ yield this.uploadReferenceAudio(project.id, data.referenceAudio);
523
+ }
524
+ if ((data === null || data === void 0 ? void 0 : data.referenceVideo) && data.referenceVideo !== true) {
525
+ yield this.uploadReferenceVideo(project.id, data.referenceVideo);
526
+ }
378
527
  });
379
528
  }
380
529
  /**
@@ -425,13 +574,13 @@ class ProjectsApi extends ApiGroup_1.default {
425
574
  return __awaiter(this, void 0, void 0, function* () {
426
575
  const imageId = (0, getUUID_1.default)();
427
576
  const presignedUrl = yield this.uploadUrl({
428
- imageId: imageId,
577
+ imageId,
429
578
  jobId: projectId,
430
579
  type: 'startingImage'
431
580
  });
432
581
  const res = yield fetch(presignedUrl, {
433
582
  method: 'PUT',
434
- body: file
583
+ body: toFetchBody(file)
435
584
  });
436
585
  if (!res.ok) {
437
586
  throw new ApiClient_1.ApiError(res.status, {
@@ -447,13 +596,13 @@ class ProjectsApi extends ApiGroup_1.default {
447
596
  return __awaiter(this, void 0, void 0, function* () {
448
597
  const imageId = (0, getUUID_1.default)();
449
598
  const presignedUrl = yield this.uploadUrl({
450
- imageId: imageId,
599
+ imageId,
451
600
  jobId: projectId,
452
601
  type: 'cnImage'
453
602
  });
454
603
  const res = yield fetch(presignedUrl, {
455
604
  method: 'PUT',
456
- body: file
605
+ body: toFetchBody(file)
457
606
  });
458
607
  if (!res.ok) {
459
608
  throw new ApiClient_1.ApiError(res.status, {
@@ -476,7 +625,7 @@ class ProjectsApi extends ApiGroup_1.default {
476
625
  });
477
626
  const res = yield fetch(presignedUrl, {
478
627
  method: 'PUT',
479
- body: file
628
+ body: toFetchBody(file)
480
629
  });
481
630
  if (!res.ok) {
482
631
  throw new ApiClient_1.ApiError(res.status, {
@@ -488,14 +637,132 @@ class ProjectsApi extends ApiGroup_1.default {
488
637
  return imageId;
489
638
  });
490
639
  }
640
+ // ============================================
641
+ // VIDEO WORKFLOW UPLOADS (WAN 2.2)
642
+ // ============================================
491
643
  /**
492
- * Estimate project cost
644
+ * Upload reference image for WAN video workflows
645
+ * @internal
646
+ */
647
+ uploadReferenceImage(projectId, file) {
648
+ return __awaiter(this, void 0, void 0, function* () {
649
+ const imageId = (0, getUUID_1.default)();
650
+ const presignedUrl = yield this.uploadUrl({
651
+ imageId,
652
+ jobId: projectId,
653
+ type: 'referenceImage'
654
+ });
655
+ const res = yield fetch(presignedUrl, {
656
+ method: 'PUT',
657
+ body: toFetchBody(file)
658
+ });
659
+ if (!res.ok) {
660
+ throw new ApiClient_1.ApiError(res.status, {
661
+ status: 'error',
662
+ errorCode: 0,
663
+ message: 'Failed to upload reference image'
664
+ });
665
+ }
666
+ return imageId;
667
+ });
668
+ }
669
+ /**
670
+ * Upload reference image end for i2v interpolation
671
+ * @internal
672
+ */
673
+ uploadReferenceImageEnd(projectId, file) {
674
+ return __awaiter(this, void 0, void 0, function* () {
675
+ const imageId = (0, getUUID_1.default)();
676
+ const presignedUrl = yield this.uploadUrl({
677
+ imageId,
678
+ jobId: projectId,
679
+ type: 'referenceImageEnd'
680
+ });
681
+ const res = yield fetch(presignedUrl, {
682
+ method: 'PUT',
683
+ body: toFetchBody(file)
684
+ });
685
+ if (!res.ok) {
686
+ throw new ApiClient_1.ApiError(res.status, {
687
+ status: 'error',
688
+ errorCode: 0,
689
+ message: 'Failed to upload reference image end'
690
+ });
691
+ }
692
+ return imageId;
693
+ });
694
+ }
695
+ /**
696
+ * Upload reference audio for s2v workflows
697
+ * Supported formats: mp3, m4a, wav
698
+ * @internal
699
+ */
700
+ uploadReferenceAudio(projectId, file) {
701
+ return __awaiter(this, void 0, void 0, function* () {
702
+ const contentType = getFileContentType(file);
703
+ const presignedUrl = yield this.mediaUploadUrl({
704
+ jobId: projectId,
705
+ type: 'referenceAudio'
706
+ });
707
+ const headers = {};
708
+ if (contentType) {
709
+ headers['Content-Type'] = contentType;
710
+ }
711
+ const res = yield fetch(presignedUrl, {
712
+ method: 'PUT',
713
+ body: toFetchBody(file),
714
+ headers
715
+ });
716
+ if (!res.ok) {
717
+ throw new ApiClient_1.ApiError(res.status, {
718
+ status: 'error',
719
+ errorCode: 0,
720
+ message: 'Failed to upload reference audio'
721
+ });
722
+ }
723
+ });
724
+ }
725
+ /**
726
+ * Upload reference video for animate workflows
727
+ * Supported formats: mp4, mov
728
+ * @internal
729
+ */
730
+ uploadReferenceVideo(projectId, file) {
731
+ return __awaiter(this, void 0, void 0, function* () {
732
+ const contentType = getFileContentType(file);
733
+ const presignedUrl = yield this.mediaUploadUrl({
734
+ jobId: projectId,
735
+ type: 'referenceVideo'
736
+ });
737
+ const headers = {};
738
+ if (contentType) {
739
+ headers['Content-Type'] = contentType;
740
+ }
741
+ const res = yield fetch(presignedUrl, {
742
+ method: 'PUT',
743
+ body: toFetchBody(file),
744
+ headers
745
+ });
746
+ if (!res.ok) {
747
+ throw new ApiClient_1.ApiError(res.status, {
748
+ status: 'error',
749
+ errorCode: 0,
750
+ message: 'Failed to upload reference video'
751
+ });
752
+ }
753
+ });
754
+ }
755
+ // ============================================
756
+ // COST ESTIMATION
757
+ // ============================================
758
+ /**
759
+ * Estimate image project cost
493
760
  */
494
761
  estimateCost(_a) {
495
- return __awaiter(this, arguments, void 0, function* ({ network, tokenType, model, imageCount, stepCount, previewCount, cnEnabled, startingImageStrength, width, height, sizePreset, guidance, scheduler, contextImages }) {
762
+ return __awaiter(this, arguments, void 0, function* ({ network, tokenType, model, imageCount, stepCount, previewCount, cnEnabled, startingImageStrength, width, height, sizePreset, guidance, sampler, contextImages }) {
496
763
  let apiVersion = 2;
497
764
  const pathParams = [
498
- tokenType || 'sogni',
765
+ tokenType || 'spark',
499
766
  network,
500
767
  model,
501
768
  imageCount,
@@ -518,21 +785,28 @@ class ProjectsApi extends ApiGroup_1.default {
518
785
  else {
519
786
  pathParams.push(0, 0);
520
787
  }
521
- if (scheduler) {
788
+ if (sampler) {
522
789
  apiVersion = 3;
523
790
  pathParams.push(guidance || 0);
524
- pathParams.push(scheduler || '');
791
+ pathParams.push((0, validation_1.validateSampler)(sampler));
525
792
  pathParams.push(contextImages || 0);
526
793
  }
527
794
  const r = yield this.client.socket.get(`/api/v${apiVersion}/job/estimate/${pathParams.join('/')}`);
528
795
  return {
529
796
  token: r.quote.project.costInToken,
530
- usd: r.quote.project.costInUSD
797
+ usd: r.quote.project.costInUSD,
798
+ spark: r.quote.project.costInSpark,
799
+ sogni: r.quote.project.costInSogni
531
800
  };
532
801
  });
533
802
  }
803
+ /**
804
+ * Estimate image enhancement cost
805
+ * @param strength
806
+ * @param tokenType
807
+ */
534
808
  estimateEnhancementCost(strength_1) {
535
- return __awaiter(this, arguments, void 0, function* (strength, tokenType = 'sogni') {
809
+ return __awaiter(this, arguments, void 0, function* (strength, tokenType = 'spark') {
536
810
  return this.estimateCost({
537
811
  network: Job_1.enhancementDefaults.network,
538
812
  tokenType,
@@ -545,10 +819,50 @@ class ProjectsApi extends ApiGroup_1.default {
545
819
  });
546
820
  });
547
821
  }
822
+ /**
823
+ * Estimates the cost of generating a video based on the provided parameters.
824
+ *
825
+ * @param {VideoEstimateRequest} params - The parameters required for video cost estimation. This includes:
826
+ * - tokenType: The type of token to be used for generation.
827
+ * - model: The model to be used for video generation.
828
+ * - width: The width of the video in pixels.
829
+ * - height: The height of the video in pixels.
830
+ * - frames: The total number of frames in the video.
831
+ * - fps: The frames per second for the video.
832
+ * - steps: Number of steps.
833
+ * @return {Promise<Object>} Returns an object containing the estimated costs for the video in different units:
834
+ * - token: Cost in tokens.
835
+ * - usd: Cost in USD.
836
+ * - spark: Cost in Spark.
837
+ * - sogni: Cost in Sogni.
838
+ */
839
+ estimateVideoCost(params) {
840
+ return __awaiter(this, void 0, void 0, function* () {
841
+ const pathParams = [
842
+ params.tokenType,
843
+ params.model,
844
+ params.width,
845
+ params.height,
846
+ params.frames,
847
+ params.fps,
848
+ params.steps
849
+ ];
850
+ const path = pathParams.map((p) => encodeURIComponent(p)).join('/');
851
+ const r = yield this.client.socket.get(`/api/v1/job-video/estimate/${path}`);
852
+ return {
853
+ token: r.quote.project.costInToken,
854
+ usd: r.quote.project.costInUSD,
855
+ spark: r.quote.project.costInSpark,
856
+ sogni: r.quote.project.costInSogni
857
+ };
858
+ });
859
+ }
860
+ // ============================================
861
+ // URL HELPERS
862
+ // ============================================
548
863
  /**
549
864
  * Get upload URL for image
550
865
  * @internal
551
- * @param params
552
866
  */
553
867
  uploadUrl(params) {
554
868
  return __awaiter(this, void 0, void 0, function* () {
@@ -559,7 +873,6 @@ class ProjectsApi extends ApiGroup_1.default {
559
873
  /**
560
874
  * Get download URL for image
561
875
  * @internal
562
- * @param params
563
876
  */
564
877
  downloadUrl(params) {
565
878
  return __awaiter(this, void 0, void 0, function* () {
@@ -567,6 +880,29 @@ class ProjectsApi extends ApiGroup_1.default {
567
880
  return r.data.downloadUrl;
568
881
  });
569
882
  }
883
+ /**
884
+ * Get upload URL for media (video/audio)
885
+ * @internal
886
+ */
887
+ mediaUploadUrl(params) {
888
+ return __awaiter(this, void 0, void 0, function* () {
889
+ const r = yield this.client.rest.get(`/v1/media/uploadUrl`, params);
890
+ return r.data.uploadUrl;
891
+ });
892
+ }
893
+ /**
894
+ * Get download URL for media (video/audio)
895
+ * @internal
896
+ */
897
+ mediaDownloadUrl(params) {
898
+ return __awaiter(this, void 0, void 0, function* () {
899
+ const r = yield this.client.rest.get(`/v1/media/downloadUrl`, params);
900
+ return r.data.downloadUrl;
901
+ });
902
+ }
903
+ // ============================================
904
+ // MODEL/PRESET HELPERS
905
+ // ============================================
570
906
  getSupportedModels() {
571
907
  return __awaiter(this, arguments, void 0, function* (forceRefresh = false) {
572
908
  if (this._supportedModels.data &&
@@ -584,7 +920,7 @@ class ProjectsApi extends ApiGroup_1.default {
584
920
  *
585
921
  * @example
586
922
  * ```ts
587
- * const presets = await client.projects.getSizePresets('fast', 'flux1-schnell-fp8');
923
+ * const presets = await sogni.projects.getSizePresets('fast', 'flux1-schnell-fp8');
588
924
  * console.log(presets);
589
925
  * ```
590
926
  *
@@ -612,6 +948,50 @@ class ProjectsApi extends ApiGroup_1.default {
612
948
  return data;
613
949
  });
614
950
  }
951
+ /**
952
+ * Retrieves the video asset configuration for a given video model identifier.
953
+ * Validates whether the provided model ID corresponds to a video model. If it does,
954
+ * returns the appropriate video asset configuration based on the workflow type.
955
+ *
956
+ * @example Returned object for a model that implements image to video workflow:
957
+ * ```json
958
+ * {
959
+ * "workflowType": "i2v",
960
+ * "assets": {
961
+ * "referenceImage": "required",
962
+ * "referenceImageEnd": "optional",
963
+ * "referenceAudio": "forbidden",
964
+ * "referenceVideo": "forbidden"
965
+ * }
966
+ * }
967
+ * ```
968
+ *
969
+ * @param {string} modelId - The identifier of the video model to retrieve the configuration for.
970
+ * @return {Object} The video asset configuration object where key is asset field and value is
971
+ * either `required`, `forbidden` or `optional`. Returns `null` if no rules defined for the model.
972
+ * @throws {ApiError} Throws an error if the provided model ID is not a video model.
973
+ */
974
+ getVideoAssetConfig(modelId) {
975
+ return __awaiter(this, void 0, void 0, function* () {
976
+ if (!this.isVideoModelId(modelId)) {
977
+ throw new ApiClient_1.ApiError(400, {
978
+ status: 'error',
979
+ errorCode: 0,
980
+ message: `Model ${modelId} is not a video model`
981
+ });
982
+ }
983
+ const workflow = (0, utils_1.getVideoWorkflowType)(modelId);
984
+ if (!workflow) {
985
+ return {
986
+ workflowType: null
987
+ };
988
+ }
989
+ return {
990
+ workflowType: workflow,
991
+ assets: utils_1.VIDEO_WORKFLOW_ASSETS[workflow]
992
+ };
993
+ });
994
+ }
615
995
  /**
616
996
  * Get available models and their worker counts. Normally, you would get list once you connect
617
997
  * to the server, but you can also call this method to get the list of available models manually.
@@ -627,7 +1007,8 @@ class ProjectsApi extends ApiGroup_1.default {
627
1007
  return {
628
1008
  id: (model === null || model === void 0 ? void 0 : model.id) || sid,
629
1009
  name: (model === null || model === void 0 ? void 0 : model.name) || sid.replace(/-/g, ' '),
630
- workerCount
1010
+ workerCount,
1011
+ media: (model === null || model === void 0 ? void 0 : model.media) || 'image'
631
1012
  };
632
1013
  });
633
1014
  });