@sogni-ai/sogni-client 4.2.0-alpha.2 → 4.2.0-alpha.21

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 (109) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/CLAUDE.md +25 -3
  3. package/README.md +411 -136
  4. package/dist/Account/index.d.ts +4 -2
  5. package/dist/Account/index.js +27 -23
  6. package/dist/Account/index.js.map +1 -1
  7. package/dist/Account/types.d.ts +7 -0
  8. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.d.ts +3 -1
  9. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js +26 -2
  10. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js.map +1 -1
  11. package/dist/ApiClient/WebSocketClient/eventSubscriptions.d.ts +33 -0
  12. package/dist/ApiClient/WebSocketClient/eventSubscriptions.js +39 -0
  13. package/dist/ApiClient/WebSocketClient/eventSubscriptions.js.map +1 -0
  14. package/dist/ApiClient/WebSocketClient/events.d.ts +24 -7
  15. package/dist/ApiClient/WebSocketClient/index.d.ts +5 -1
  16. package/dist/ApiClient/WebSocketClient/index.js +24 -1
  17. package/dist/ApiClient/WebSocketClient/index.js.map +1 -1
  18. package/dist/ApiClient/WebSocketClient/messages.d.ts +2 -0
  19. package/dist/ApiClient/WebSocketClient/types.d.ts +2 -0
  20. package/dist/ApiClient/index.d.ts +6 -1
  21. package/dist/ApiClient/index.js +7 -3
  22. package/dist/ApiClient/index.js.map +1 -1
  23. package/dist/Chat/ChatTools.d.ts +5 -49
  24. package/dist/Chat/ChatTools.js +311 -88
  25. package/dist/Chat/ChatTools.js.map +1 -1
  26. package/dist/Chat/index.d.ts +11 -2
  27. package/dist/Chat/index.js +78 -4
  28. package/dist/Chat/index.js.map +1 -1
  29. package/dist/Chat/modelRouting.d.ts +100 -0
  30. package/dist/Chat/modelRouting.js +441 -0
  31. package/dist/Chat/modelRouting.js.map +1 -0
  32. package/dist/Chat/sogniHostedTools.generated.json +529 -0
  33. package/dist/Chat/tools.d.ts +9 -55
  34. package/dist/Chat/tools.js +72 -228
  35. package/dist/Chat/tools.js.map +1 -1
  36. package/dist/Chat/types.d.ts +91 -2
  37. package/dist/CreativeWorkflows/index.d.ts +23 -0
  38. package/dist/CreativeWorkflows/index.js +274 -0
  39. package/dist/CreativeWorkflows/index.js.map +1 -0
  40. package/dist/CreativeWorkflows/types.d.ts +106 -0
  41. package/dist/CreativeWorkflows/types.js +3 -0
  42. package/dist/CreativeWorkflows/types.js.map +1 -0
  43. package/dist/Projects/Job.d.ts +6 -0
  44. package/dist/Projects/Job.js +60 -5
  45. package/dist/Projects/Job.js.map +1 -1
  46. package/dist/Projects/Project.js +15 -3
  47. package/dist/Projects/Project.js.map +1 -1
  48. package/dist/Projects/createJobRequestMessage.js +140 -6
  49. package/dist/Projects/createJobRequestMessage.js.map +1 -1
  50. package/dist/Projects/index.d.ts +10 -1
  51. package/dist/Projects/index.js +197 -58
  52. package/dist/Projects/index.js.map +1 -1
  53. package/dist/Projects/types/ModelOptions.d.ts +3 -3
  54. package/dist/Projects/types/ModelOptions.js +12 -5
  55. package/dist/Projects/types/ModelOptions.js.map +1 -1
  56. package/dist/Projects/types/ModelTiersRaw.d.ts +7 -7
  57. package/dist/Projects/types/RawProject.d.ts +2 -0
  58. package/dist/Projects/types/events.d.ts +5 -4
  59. package/dist/Projects/types/index.d.ts +77 -7
  60. package/dist/Projects/types/index.js.map +1 -1
  61. package/dist/Projects/utils/index.d.ts +8 -1
  62. package/dist/Projects/utils/index.js +22 -8
  63. package/dist/Projects/utils/index.js.map +1 -1
  64. package/dist/index.d.ts +28 -3
  65. package/dist/index.js +19 -1
  66. package/dist/index.js.map +1 -1
  67. package/dist/lib/RestClient.d.ts +4 -1
  68. package/dist/lib/RestClient.js +17 -9
  69. package/dist/lib/RestClient.js.map +1 -1
  70. package/dist/lib/mediaValidation.d.ts +16 -0
  71. package/dist/lib/mediaValidation.js +280 -0
  72. package/dist/lib/mediaValidation.js.map +1 -0
  73. package/dist/lib/validation.d.ts +6 -1
  74. package/dist/lib/validation.js +28 -2
  75. package/dist/lib/validation.js.map +1 -1
  76. package/llms-full.txt +372 -133
  77. package/llms.txt +197 -86
  78. package/package.json +13 -4
  79. package/src/Account/index.ts +22 -2
  80. package/src/Account/types.ts +7 -0
  81. package/src/ApiClient/WebSocketClient/BrowserWebSocketClient/index.ts +47 -3
  82. package/src/ApiClient/WebSocketClient/eventSubscriptions.ts +92 -0
  83. package/src/ApiClient/WebSocketClient/events.ts +25 -7
  84. package/src/ApiClient/WebSocketClient/index.ts +33 -1
  85. package/src/ApiClient/WebSocketClient/messages.ts +2 -0
  86. package/src/ApiClient/WebSocketClient/types.ts +2 -0
  87. package/src/ApiClient/index.ts +32 -2
  88. package/src/Chat/ChatTools.ts +395 -95
  89. package/src/Chat/index.ts +149 -5
  90. package/src/Chat/modelRouting.ts +602 -0
  91. package/src/Chat/sogniHostedTools.generated.json +529 -0
  92. package/src/Chat/tools.ts +98 -245
  93. package/src/Chat/types.ts +100 -2
  94. package/src/CreativeWorkflows/index.ts +290 -0
  95. package/src/CreativeWorkflows/types.ts +134 -0
  96. package/src/Projects/Job.ts +76 -5
  97. package/src/Projects/Project.ts +13 -3
  98. package/src/Projects/createJobRequestMessage.ts +152 -13
  99. package/src/Projects/index.ts +230 -52
  100. package/src/Projects/types/ModelOptions.ts +15 -8
  101. package/src/Projects/types/ModelTiersRaw.ts +7 -7
  102. package/src/Projects/types/RawProject.ts +2 -0
  103. package/src/Projects/types/events.ts +5 -4
  104. package/src/Projects/types/index.ts +86 -6
  105. package/src/Projects/utils/index.ts +24 -8
  106. package/src/index.ts +93 -0
  107. package/src/lib/RestClient.ts +15 -5
  108. package/src/lib/mediaValidation.ts +367 -0
  109. package/src/lib/validation.ts +38 -2
@@ -70,6 +70,29 @@ function getFileContentType(file: File | Buffer | Blob): string | undefined {
70
70
  if (file instanceof Blob && 'type' in file && file.type) {
71
71
  return file.type;
72
72
  }
73
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(file)) {
74
+ if (file.length >= 12) {
75
+ if (file[0] === 0xff && file[1] === 0xd8 && file[2] === 0xff) return 'image/jpeg';
76
+ if (file[0] === 0x89 && file[1] === 0x50 && file[2] === 0x4e && file[3] === 0x47) {
77
+ return 'image/png';
78
+ }
79
+ if (file.toString('ascii', 0, 4) === 'RIFF' && file.toString('ascii', 8, 12) === 'WEBP') {
80
+ return 'image/webp';
81
+ }
82
+ if (file.toString('ascii', 0, 3) === 'GIF') return 'image/gif';
83
+ if (file.toString('ascii', 4, 8) === 'ftyp') {
84
+ const brand = file.toString('ascii', 8, 12).toLowerCase();
85
+ if (brand.includes('m4a') || brand.includes('m4b')) return 'audio/mp4';
86
+ if (brand.includes('qt')) return 'video/quicktime';
87
+ return 'video/mp4';
88
+ }
89
+ if (file.toString('ascii', 0, 4) === 'RIFF' && file.toString('ascii', 8, 12) === 'WAVE') {
90
+ return 'audio/wav';
91
+ }
92
+ }
93
+ if (file.length >= 3 && file.toString('ascii', 0, 3) === 'ID3') return 'audio/mpeg';
94
+ if (file.length >= 2 && file[0] === 0xff && (file[1] & 0xe0) === 0xe0) return 'audio/mpeg';
95
+ }
73
96
  return undefined;
74
97
  }
75
98
 
@@ -282,15 +305,17 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
282
305
  }
283
306
 
284
307
  private async handleJobProgress(data: JobProgressData) {
285
- this.emit('job', {
308
+ const event: JobEvent = {
286
309
  type: 'progress',
287
310
  projectId: data.jobID,
288
311
  jobId: data.imgID,
289
- step: data.step,
290
- stepCount: data.stepCount
291
- });
312
+ ...(typeof data.step === 'number' ? { step: data.step } : {}),
313
+ ...(typeof data.stepCount === 'number' ? { stepCount: data.stepCount } : {}),
314
+ ...(typeof data.progress === 'number' ? { progress: data.progress } : {})
315
+ };
316
+ this.emit('job', event);
292
317
 
293
- if (data.hasImage) {
318
+ if (data.hasImage === true) {
294
319
  this.downloadUrl({
295
320
  jobId: data.jobID,
296
321
  imageId: data.imgID,
@@ -318,7 +343,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
318
343
  private async handleJobResult(data: JobResultData) {
319
344
  const project = this.projects.find((p) => p.id === data.jobID);
320
345
  const passNSFWCheck = !data.triggeredNSFWFilter || !project || project.params.disableNSFWFilter;
321
- let downloadUrl = data.resultUrl || null; // Use resultUrl from event if provided
346
+ let downloadUrl = data.resultUrl || data.videoUrl || data.videoFile || null; // Use result URL from event if provided
322
347
 
323
348
  // If no resultUrl provided and NSFW check passes, generate download URL
324
349
  if (!downloadUrl && passNSFWCheck && !data.userCanceled) {
@@ -350,16 +375,25 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
350
375
  }
351
376
 
352
377
  // Update the job directly with the result URL to prevent duplicate API calls
378
+ let performedStepCount = data.performedStepCount;
379
+ let seed = data.lastSeed !== undefined ? Number(data.lastSeed) : undefined;
353
380
  if (project) {
354
381
  const job = project.job(data.imgID);
355
382
  if (job) {
383
+ performedStepCount =
384
+ typeof performedStepCount === 'number'
385
+ ? performedStepCount
386
+ : job.stepCount > 0
387
+ ? job.stepCount
388
+ : job.step;
389
+ seed = typeof seed === 'number' && Number.isFinite(seed) ? seed : job.seed;
356
390
  job._update({
357
391
  status: data.userCanceled ? 'canceled' : 'completed',
358
- step: data.performedStepCount,
359
- seed: Number(data.lastSeed),
392
+ step: performedStepCount,
393
+ seed,
360
394
  resultUrl: downloadUrl,
361
- isNSFW: data.triggeredNSFWFilter,
362
- userCanceled: data.userCanceled
395
+ isNSFW: Boolean(data.triggeredNSFWFilter),
396
+ userCanceled: Boolean(data.userCanceled)
363
397
  });
364
398
  }
365
399
  }
@@ -369,11 +403,11 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
369
403
  type: 'completed',
370
404
  projectId: data.jobID,
371
405
  jobId: data.imgID,
372
- steps: data.performedStepCount,
373
- seed: Number(data.lastSeed),
406
+ ...(typeof performedStepCount === 'number' ? { steps: performedStepCount } : {}),
407
+ ...(typeof seed === 'number' && Number.isFinite(seed) ? { seed } : {}),
374
408
  resultUrl: downloadUrl,
375
- isNSFW: data.triggeredNSFWFilter,
376
- userCanceled: data.userCanceled
409
+ isNSFW: Boolean(data.triggeredNSFWFilter),
410
+ userCanceled: Boolean(data.userCanceled)
377
411
  });
378
412
  }
379
413
 
@@ -483,12 +517,27 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
483
517
  });
484
518
  break;
485
519
  case 'progress':
486
- job._update({
487
- status: 'processing',
488
- // Just in case event comes out of order
489
- step: Math.max(event.step, job.step),
490
- stepCount: event.stepCount
491
- });
520
+ {
521
+ const delta: {
522
+ status: 'processing';
523
+ step?: number;
524
+ stepCount?: number;
525
+ externalProgress?: number;
526
+ } = {
527
+ status: 'processing'
528
+ };
529
+ if (typeof event.step === 'number') {
530
+ // Just in case event comes out of order
531
+ delta.step = Math.max(event.step, job.step);
532
+ }
533
+ if (typeof event.stepCount === 'number') {
534
+ delta.stepCount = event.stepCount;
535
+ }
536
+ if (typeof event.progress === 'number') {
537
+ delta.externalProgress = event.progress;
538
+ }
539
+ job._update(delta);
540
+ }
492
541
  if (project.status !== 'processing') {
493
542
  project._update({ status: 'processing' });
494
543
  }
@@ -515,13 +564,29 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
515
564
  job._update({ previewUrl: event.url });
516
565
  break;
517
566
  case 'completed': {
518
- job._update({
567
+ const delta: {
568
+ status: 'completed' | 'canceled';
569
+ resultUrl: string | null;
570
+ isNSFW: boolean;
571
+ userCanceled: boolean;
572
+ step?: number;
573
+ seed?: number;
574
+ } = {
519
575
  status: event.userCanceled ? 'canceled' : 'completed',
520
- step: event.steps,
521
- seed: event.seed,
522
576
  resultUrl: event.resultUrl,
523
577
  isNSFW: event.isNSFW,
524
578
  userCanceled: event.userCanceled
579
+ };
580
+ if (typeof event.steps === 'number') {
581
+ delta.step = event.steps;
582
+ } else if (job.stepCount > 0) {
583
+ delta.step = job.stepCount;
584
+ }
585
+ if (typeof event.seed === 'number' && Number.isFinite(event.seed)) {
586
+ delta.seed = event.seed;
587
+ }
588
+ job._update({
589
+ ...delta
525
590
  });
526
591
  break;
527
592
  }
@@ -592,7 +657,11 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
592
657
  async create(data: ProjectParams): Promise<Project> {
593
658
  const project = new Project({ ...data }, { api: this, logger: this.client.logger });
594
659
  const modelOptions = await this.getModelOptions(data.modelId);
595
- const request = createJobRequestMessage(project.id, data, modelOptions);
660
+ const requestParams = {
661
+ ...data,
662
+ appSource: data.appSource || this.client.appSource
663
+ } as ProjectParams;
664
+ const request = createJobRequestMessage(project.id, requestParams, modelOptions);
596
665
 
597
666
  switch (data.type) {
598
667
  case 'image':
@@ -600,6 +669,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
600
669
  break;
601
670
  case 'video':
602
671
  await this._processVideoAssets(project, data);
672
+ this._annotateVideoAssetContentTypes(request, data);
603
673
  break;
604
674
  case 'audio':
605
675
  // No assets to upload for audio
@@ -621,7 +691,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
621
691
  await this.uploadCNImage(project.id, data.controlNet.image);
622
692
  }
623
693
 
624
- // Context images (Flux.2 Dev supports up to 6; Qwen Image Edit Plus supports up to 3; Flux Kontext supports up to 2)
694
+ // Context images (GPT Image 2 supports up to 16; Flux.2 Dev supports up to 6; Qwen Image Edit Plus supports up to 3; Flux Kontext supports up to 2)
625
695
  if (data.contextImages?.length) {
626
696
  const maxContextImages = getMaxContextImages(data.modelId);
627
697
  if (data.contextImages.length > maxContextImages) {
@@ -634,7 +704,11 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
634
704
  await Promise.all(
635
705
  data.contextImages.map((image, index) => {
636
706
  if (image && image !== true) {
637
- return this.uploadContextImage(project.id, index as 0 | 1 | 2 | 3 | 4 | 5, image);
707
+ return this.uploadContextImage(
708
+ project.id,
709
+ index as 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15,
710
+ image
711
+ );
638
712
  }
639
713
  })
640
714
  );
@@ -659,6 +733,29 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
659
733
  }
660
734
  }
661
735
 
736
+ private _annotateVideoAssetContentTypes(request: Record<string, any>, data: VideoProjectParams) {
737
+ const keyFrame = request.keyFrames?.[0];
738
+ if (!keyFrame) return;
739
+
740
+ if (data.referenceImage && data.referenceImage !== true) {
741
+ keyFrame.referenceImageContentType = getFileContentType(data.referenceImage);
742
+ }
743
+ if (data.referenceImageEnd && data.referenceImageEnd !== true) {
744
+ keyFrame.referenceImageEndContentType = getFileContentType(data.referenceImageEnd);
745
+ }
746
+ if (data.referenceAudio && data.referenceAudio !== true) {
747
+ keyFrame.referenceAudioContentType = getFileContentType(data.referenceAudio);
748
+ }
749
+ if (data.referenceAudioIdentity && data.referenceAudioIdentity !== true) {
750
+ const contentType = getFileContentType(data.referenceAudioIdentity);
751
+ keyFrame.referenceAudioIdentityContentType = contentType;
752
+ keyFrame.referenceAudioContentType ??= contentType;
753
+ }
754
+ if (data.referenceVideo && data.referenceVideo !== true) {
755
+ keyFrame.referenceVideoContentType = getFileContentType(data.referenceVideo);
756
+ }
757
+ }
758
+
662
759
  /**
663
760
  * Get project by id, this API returns project data from the server only if the project is
664
761
  * completed or failed. If the project is still processing, it will throw 404 error.
@@ -703,16 +800,36 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
703
800
  }
704
801
  }
705
802
 
803
+ /**
804
+ * Notify the socket server to cancel a project this client has timed out waiting for.
805
+ * This preserves the local timeout failure state while still using the normal artist
806
+ * cancellation protocol so the server aborts worker/vendor-side work.
807
+ * @internal
808
+ */
809
+ async _notifyProjectTimedOut(projectId: string) {
810
+ await this.client.socket.send('jobError', {
811
+ jobID: projectId,
812
+ error: 'artistCanceled',
813
+ error_message: 'artistCanceled',
814
+ isFromWorker: false
815
+ });
816
+ }
817
+
706
818
  private async uploadGuideImage(projectId: string, file: File | Buffer | Blob) {
707
819
  const imageId = getUUID();
820
+ const contentType = getFileContentType(file);
708
821
  const presignedUrl = await this.uploadUrl({
709
822
  imageId,
710
823
  jobId: projectId,
711
- type: 'startingImage'
824
+ type: 'startingImage',
825
+ contentType
712
826
  });
827
+ const headers: Record<string, string> = {};
828
+ if (contentType) headers['Content-Type'] = contentType;
713
829
  const res = await fetch(presignedUrl, {
714
830
  method: 'PUT',
715
- body: toFetchBody(file)
831
+ body: toFetchBody(file),
832
+ headers
716
833
  });
717
834
  if (!res.ok) {
718
835
  throw new ApiError(res.status, {
@@ -726,14 +843,19 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
726
843
 
727
844
  private async uploadCNImage(projectId: string, file: File | Buffer | Blob) {
728
845
  const imageId = getUUID();
846
+ const contentType = getFileContentType(file);
729
847
  const presignedUrl = await this.uploadUrl({
730
848
  imageId,
731
849
  jobId: projectId,
732
- type: 'cnImage'
850
+ type: 'cnImage',
851
+ contentType
733
852
  });
853
+ const headers: Record<string, string> = {};
854
+ if (contentType) headers['Content-Type'] = contentType;
734
855
  const res = await fetch(presignedUrl, {
735
856
  method: 'PUT',
736
- body: toFetchBody(file)
857
+ body: toFetchBody(file),
858
+ headers
737
859
  });
738
860
  if (!res.ok) {
739
861
  throw new ApiError(res.status, {
@@ -747,20 +869,41 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
747
869
 
748
870
  private async uploadContextImage(
749
871
  projectId: string,
750
- index: 0 | 1 | 2 | 3 | 4 | 5,
872
+ index: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15,
751
873
  file: File | Buffer | Blob
752
874
  ) {
753
875
  const imageId = getUUID();
754
- const imageIndex = (index + 1) as 1 | 2 | 3 | 4 | 5 | 6;
876
+ const imageIndex = (index + 1) as
877
+ | 1
878
+ | 2
879
+ | 3
880
+ | 4
881
+ | 5
882
+ | 6
883
+ | 7
884
+ | 8
885
+ | 9
886
+ | 10
887
+ | 11
888
+ | 12
889
+ | 13
890
+ | 14
891
+ | 15
892
+ | 16;
893
+ const contentType = getFileContentType(file);
755
894
  const presignedUrl = await this.uploadUrl({
756
895
  imageId,
757
896
  jobId: projectId,
758
- type: `contextImage${imageIndex}`
897
+ type: `contextImage${imageIndex}`,
898
+ contentType
759
899
  });
760
900
  const body = toFetchBody(file);
901
+ const headers: Record<string, string> = {};
902
+ if (contentType) headers['Content-Type'] = contentType;
761
903
  const res = await fetch(presignedUrl, {
762
904
  method: 'PUT',
763
- body
905
+ body,
906
+ headers
764
907
  });
765
908
  if (!res.ok) {
766
909
  throw new ApiError(res.status, {
@@ -782,14 +925,19 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
782
925
  */
783
926
  private async uploadReferenceImage(projectId: string, file: File | Buffer | Blob) {
784
927
  const imageId = getUUID();
928
+ const contentType = getFileContentType(file);
785
929
  const presignedUrl = await this.uploadUrl({
786
930
  imageId,
787
931
  jobId: projectId,
788
- type: 'referenceImage'
932
+ type: 'referenceImage',
933
+ contentType
789
934
  });
935
+ const headers: Record<string, string> = {};
936
+ if (contentType) headers['Content-Type'] = contentType;
790
937
  const res = await fetch(presignedUrl, {
791
938
  method: 'PUT',
792
- body: toFetchBody(file)
939
+ body: toFetchBody(file),
940
+ headers
793
941
  });
794
942
  if (!res.ok) {
795
943
  throw new ApiError(res.status, {
@@ -807,14 +955,19 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
807
955
  */
808
956
  private async uploadReferenceImageEnd(projectId: string, file: File | Buffer | Blob) {
809
957
  const imageId = getUUID();
958
+ const contentType = getFileContentType(file);
810
959
  const presignedUrl = await this.uploadUrl({
811
960
  imageId,
812
961
  jobId: projectId,
813
- type: 'referenceImageEnd'
962
+ type: 'referenceImageEnd',
963
+ contentType
814
964
  });
965
+ const headers: Record<string, string> = {};
966
+ if (contentType) headers['Content-Type'] = contentType;
815
967
  const res = await fetch(presignedUrl, {
816
968
  method: 'PUT',
817
- body: toFetchBody(file)
969
+ body: toFetchBody(file),
970
+ headers
818
971
  });
819
972
  if (!res.ok) {
820
973
  throw new ApiError(res.status, {
@@ -836,7 +989,8 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
836
989
  const contentType = getFileContentType(file);
837
990
  const presignedUrl = await this.mediaUploadUrl({
838
991
  jobId: projectId,
839
- type: 'referenceAudio'
992
+ type: 'referenceAudio',
993
+ contentType
840
994
  });
841
995
  const headers: Record<string, string> = {};
842
996
  if (contentType) {
@@ -865,7 +1019,8 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
865
1019
  const contentType = getFileContentType(file);
866
1020
  const presignedUrl = await this.mediaUploadUrl({
867
1021
  jobId: projectId,
868
- type: 'referenceVideo'
1022
+ type: 'referenceVideo',
1023
+ contentType
869
1024
  });
870
1025
  const headers: Record<string, string> = {};
871
1026
  if (contentType) {
@@ -906,7 +1061,9 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
906
1061
  sizePreset,
907
1062
  guidance,
908
1063
  sampler,
909
- contextImages
1064
+ contextImages,
1065
+ gptImageQuality,
1066
+ outputFormat
910
1067
  }: EstimateRequest): Promise<CostEstimation> {
911
1068
  let apiVersion = 2;
912
1069
  const modelOptions = await this.getModelOptions(model);
@@ -932,14 +1089,18 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
932
1089
  } else {
933
1090
  pathParams.push(0, 0);
934
1091
  }
935
- if (sampler) {
1092
+ if (sampler || contextImages !== undefined) {
936
1093
  apiVersion = 3;
937
1094
  pathParams.push(guidance || 0);
938
- pathParams.push(validateSampler(sampler, modelOptions)!);
1095
+ pathParams.push(sampler ? validateSampler(sampler, modelOptions)! : '_');
939
1096
  pathParams.push(contextImages || 0);
940
1097
  }
1098
+ const queryParams = new URLSearchParams();
1099
+ if (gptImageQuality) queryParams.set('gptImageQuality', gptImageQuality);
1100
+ if (outputFormat) queryParams.set('outputFormat', outputFormat);
1101
+ const query = queryParams.toString();
941
1102
  const r = await this.client.socket.get<EstimationResponse>(
942
- `/api/v${apiVersion}/job/estimate/${pathParams.join('/')}`
1103
+ `/api/v${apiVersion}/job/estimate/${pathParams.join('/')}${query ? `?${query}` : ''}`
943
1104
  );
944
1105
  return {
945
1106
  token: r.quote.project.costInToken,
@@ -978,6 +1139,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
978
1139
  * - frames: The total number of frames in the video.
979
1140
  * - fps: The frames per second for the video.
980
1141
  * - steps: Number of steps.
1142
+ * - hasVideoInput: Whether to price a Seedance estimate with video input.
981
1143
  * @return {Promise<Object>} Returns an object containing the estimated costs for the video in different units:
982
1144
  * - token: Cost in tokens.
983
1145
  * - usd: Cost in USD.
@@ -985,21 +1147,37 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
985
1147
  * - sogni: Cost in Sogni.
986
1148
  */
987
1149
  async estimateVideoCost(params: VideoEstimateRequest) {
988
- const pathParams = [
1150
+ const frames = params.frames
1151
+ ? params.frames
1152
+ : calculateVideoFrames(params.model, params.duration, params.fps);
1153
+ const numberOfMedia = params.numberOfMedia ?? 1;
1154
+ const pathParams: Array<string | number> = [
989
1155
  params.tokenType,
990
1156
  params.model,
991
1157
  params.width,
992
1158
  params.height,
993
- params.frames
994
- ? params.frames
995
- : calculateVideoFrames(params.model, params.duration, params.fps),
996
- params.fps,
997
- params.steps,
998
- params.numberOfMedia
1159
+ frames,
1160
+ params.fps
999
1161
  ];
1162
+ if (params.steps !== undefined && params.steps !== null) {
1163
+ pathParams.push(params.steps);
1164
+ pathParams.push(numberOfMedia);
1165
+ } else if (numberOfMedia !== 1) {
1166
+ pathParams.push(0);
1167
+ pathParams.push(numberOfMedia);
1168
+ }
1000
1169
  const path = pathParams.map((p) => encodeURIComponent(p)).join('/');
1170
+ const query = new URLSearchParams();
1171
+ const hasVideoInput =
1172
+ params.hasVideoInput === true ||
1173
+ Boolean(params.referenceVideo) ||
1174
+ (Array.isArray(params.referenceVideoUrls) && params.referenceVideoUrls.length > 0);
1175
+ if (hasVideoInput) {
1176
+ query.set('hasVideoInput', '1');
1177
+ }
1178
+ const queryString = query.toString();
1001
1179
  const r = await this.client.socket.get<EstimationResponse>(
1002
- `/api/v1/job-video/estimate/${path}`
1180
+ `/api/v1/job-video/estimate/${path}${queryString ? `?${queryString}` : ''}`
1003
1181
  );
1004
1182
  return {
1005
1183
  token: r.quote.project.costInToken,
@@ -29,9 +29,9 @@ export interface ImageModelOptions {
29
29
 
30
30
  export interface VideoModelOptions {
31
31
  type: 'video';
32
- steps: NumRange;
33
- guidance: NumRange;
34
- fps: Options<number>;
32
+ steps?: NumRange;
33
+ guidance?: NumRange;
34
+ fps?: Options<number>;
35
35
  sampler: Options<string>;
36
36
  scheduler: Options<string>;
37
37
  }
@@ -98,14 +98,21 @@ export function mapComfyImageTier(tier: ComfyImageTier): ImageModelOptions {
98
98
  }
99
99
 
100
100
  export function mapVideoTier(tier: VideoTier): VideoModelOptions {
101
- return {
101
+ const options: VideoModelOptions = {
102
102
  type: 'video',
103
- steps: mapRange(tier.steps),
104
- guidance: mapRange(tier.guidance),
105
103
  scheduler: mapOptions(tier.comfyScheduler, schedulerValueToAlias),
106
- sampler: mapOptions(tier.comfySampler, samplerValueToAlias),
107
- fps: tier.fps
104
+ sampler: mapOptions(tier.comfySampler, samplerValueToAlias)
108
105
  };
106
+ if (tier.steps) {
107
+ options.steps = mapRange(tier.steps);
108
+ }
109
+ if (tier.guidance) {
110
+ options.guidance = mapRange(tier.guidance);
111
+ }
112
+ if (tier.fps) {
113
+ options.fps = tier.fps;
114
+ }
115
+ return options;
109
116
  }
110
117
 
111
118
  export function mapAudioTier(tier: AudioTier): AudioModelOptions {
@@ -56,14 +56,14 @@ export interface VideoTier {
56
56
  audioDuration?: DurationDefaults;
57
57
  audioStart?: DurationDefaults;
58
58
  benchmark: Benchmark;
59
- comfySampler: StringDefaults;
60
- comfyScheduler: StringDefaults;
61
- fps: NumericOptions;
62
- frames: NumericDefaults;
63
- guidance: NumericDefaults;
59
+ comfySampler?: StringDefaults;
60
+ comfyScheduler?: StringDefaults;
61
+ fps?: NumericOptions;
62
+ frames?: NumericDefaults;
63
+ guidance?: NumericDefaults;
64
64
  height: NumericDefaults;
65
- shift: NumericDefaults;
66
- steps: NumericDefaults;
65
+ shift?: NumericDefaults;
66
+ steps?: NumericDefaults;
67
67
  type: 'video';
68
68
  videoStart?: DurationDefaults;
69
69
  width: NumericDefaults;
@@ -58,6 +58,8 @@ export interface RawJob {
58
58
  costActual: CostActual;
59
59
  network: SupernetType;
60
60
  txId?: string;
61
+ resultUrl?: string | null;
62
+ resultKey?: string | null;
61
63
  }
62
64
 
63
65
  export interface CostActual {
@@ -44,8 +44,9 @@ export interface JobStarted extends JobEventBase {
44
44
 
45
45
  export interface JobProgress extends JobEventBase {
46
46
  type: 'progress';
47
- step: number;
48
- stepCount: number;
47
+ step?: number;
48
+ stepCount?: number;
49
+ progress?: number;
49
50
  }
50
51
 
51
52
  export interface JobETA extends JobEventBase {
@@ -60,8 +61,8 @@ export interface JobPreview extends JobEventBase {
60
61
 
61
62
  export interface JobCompleted extends JobEventBase {
62
63
  type: 'completed';
63
- steps: number;
64
- seed: number;
64
+ steps?: number;
65
+ seed?: number;
65
66
  /**
66
67
  * URL to the result image, could be null if the job was canceled or triggered NSFW filter while
67
68
  * it was not disabled by the user