@sogni-ai/sogni-client 5.84.1 → 5.84.2

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,85 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { ApiError } from '../../ApiClient/index.js';
11
+ /**
12
+ * Refuse an image-slot upload whose contents are text instead of image bytes.
13
+ *
14
+ * An image slot that receives text instead of an image (a file path string,
15
+ * a fetched HTML page, or base64 text) cannot be decoded, so the job fails
16
+ * after it has been submitted. Checking the leading bytes here turns that
17
+ * into an error that names the slot, before any upload URL, upload or job
18
+ * request.
19
+ *
20
+ * Only contents that cannot be an image are refused: a `data:` URL, an HTML
21
+ * page, base64 text, or a string that is not markup. Image bytes, SVG markup
22
+ * and anything this check does not recognise upload as before.
23
+ */
24
+ export function assertImageUploadBody(file, slot) {
25
+ return __awaiter(this, void 0, void 0, function* () {
26
+ const found = yield describeTextBody(file);
27
+ if (!found)
28
+ return;
29
+ throw new ApiError(400, {
30
+ status: 'error',
31
+ errorCode: 0,
32
+ message: `${slot} is not image data: ${found}. Upload the image file itself.`
33
+ });
34
+ });
35
+ }
36
+ /** Enough leading bytes for an HTML doctype after a byte-order mark and some whitespace. */
37
+ const SNIFF_BYTES = 64;
38
+ /** Sixteen base64 characters: no binary image header is made only of them. */
39
+ const BASE64_HEAD = /^[A-Za-z0-9+/]{16}/;
40
+ const DATA_URL_HEAD = /^data:/i;
41
+ /** UTF-8 byte-order mark, read either as one character or as its three bytes. */
42
+ const HTML_HEAD = /^(?:|\xef\xbb\xbf)?\s*<(?:!doctype\s+html|html[\s>])/i;
43
+ const MARKUP_HEAD = /^(?:|\xef\xbb\xbf)?\s*</;
44
+ function describeTextBody(file) {
45
+ return __awaiter(this, void 0, void 0, function* () {
46
+ var _a;
47
+ if (typeof file === 'string') {
48
+ const head = file.slice(0, SNIFF_BYTES);
49
+ // SVG markup is text and still uploads; any other string is not an image.
50
+ return (_a = describeHead(head)) !== null && _a !== void 0 ? _a : (MARKUP_HEAD.test(head) ? null : 'it is a string, not the file');
51
+ }
52
+ const bytes = yield leadingBytes(file);
53
+ return bytes ? describeHead(String.fromCharCode(...bytes)) : null;
54
+ });
55
+ }
56
+ function leadingBytes(file) {
57
+ return __awaiter(this, void 0, void 0, function* () {
58
+ try {
59
+ if (typeof Blob !== 'undefined' && file instanceof Blob) {
60
+ return new Uint8Array(yield file.slice(0, SNIFF_BYTES).arrayBuffer());
61
+ }
62
+ if (ArrayBuffer.isView(file)) {
63
+ return new Uint8Array(file.buffer, file.byteOffset, Math.min(file.byteLength, SNIFF_BYTES));
64
+ }
65
+ if (file instanceof ArrayBuffer) {
66
+ return new Uint8Array(file, 0, Math.min(file.byteLength, SNIFF_BYTES));
67
+ }
68
+ }
69
+ catch (_a) {
70
+ // A file that can no longer be read fails at the upload, as it always has.
71
+ }
72
+ return null;
73
+ });
74
+ }
75
+ function describeHead(head) {
76
+ if (DATA_URL_HEAD.test(head))
77
+ return 'its contents are a data: URL';
78
+ if (HTML_HEAD.test(head))
79
+ return 'its contents are an HTML page';
80
+ // GIF's signature is letters and digits, and its size bytes can be too.
81
+ if (!head.startsWith('GIF8') && BASE64_HEAD.test(head))
82
+ return 'its contents are base64 text';
83
+ return null;
84
+ }
85
+ //# sourceMappingURL=imageUploadBody.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imageUploadBody.js","sourceRoot":"","sources":["../../../src/Projects/utils/imageUploadBody.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAgB,qBAAqB,CAAC,IAAa,EAAE,IAAY;;QACrE,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE;YACtB,MAAM,EAAE,OAAO;YACf,SAAS,EAAE,CAAC;YACZ,OAAO,EAAE,GAAG,IAAI,uBAAuB,KAAK,iCAAiC;SAC9E,CAAC,CAAC;IACL,CAAC;CAAA;AAED,4FAA4F;AAC5F,MAAM,WAAW,GAAG,EAAE,CAAC;AACvB,8EAA8E;AAC9E,MAAM,WAAW,GAAG,oBAAoB,CAAC;AACzC,MAAM,aAAa,GAAG,SAAS,CAAC;AAChC,iFAAiF;AACjF,MAAM,SAAS,GAAG,wDAAwD,CAAC;AAC3E,MAAM,WAAW,GAAG,0BAA0B,CAAC;AAE/C,SAAe,gBAAgB,CAAC,IAAa;;;QAC3C,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;YACxC,0EAA0E;YAC1E,OAAO,MAAA,YAAY,CAAC,IAAI,CAAC,mCAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC;QAChG,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;QACvC,OAAO,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACpE,CAAC;CAAA;AAED,SAAe,YAAY,CAAC,IAAa;;QACvC,IAAI,CAAC;YACH,IAAI,OAAO,IAAI,KAAK,WAAW,IAAI,IAAI,YAAY,IAAI,EAAE,CAAC;gBACxD,OAAO,IAAI,UAAU,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;YACxE,CAAC;YACD,IAAI,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC7B,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC;YAC9F,CAAC;YACD,IAAI,IAAI,YAAY,WAAW,EAAE,CAAC;gBAChC,OAAO,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC;YACzE,CAAC;QACH,CAAC;QAAC,WAAM,CAAC;YACP,2EAA2E;QAC7E,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CAAA;AAED,SAAS,YAAY,CAAC,IAAY;IAChC,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,8BAA8B,CAAC;IACpE,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,+BAA+B,CAAC;IACjE,wEAAwE;IACxE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,8BAA8B,CAAC;IAC9F,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -1,4 +1,4 @@
1
1
  // AUTO-GENERATED by scripts/generate-version.mjs — do not edit.
2
2
  // Source of truth: package.json version.
3
- export const LIB_VERSION = '5.84.1';
3
+ export const LIB_VERSION = '5.84.2';
4
4
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "5.84.1",
6
+ "version": "5.84.2",
7
7
  "engines": {
8
8
  "node": ">=22"
9
9
  },
@@ -92,6 +92,7 @@
92
92
  "test:prompt-expansion": "npm run build && node scripts/check-prompt-expansion-transport.cjs",
93
93
  "test:priority-mode": "npm run build && node scripts/check-priority-mode.cjs && node scripts/check-priority-capability.cjs",
94
94
  "test:reference-content-types": "npm run build && node scripts/check-reference-content-types.cjs",
95
+ "test:image-upload-body": "npm run build && node scripts/check-image-upload-body.cjs",
95
96
  "test:minimax-h3-audio-policy": "node scripts/check-minimax-h3-source-audio-policy.cjs",
96
97
  "test:minimax-h3-balanced": "npm run build && node scripts/check-minimax-h3-balanced.cjs",
97
98
  "test:minimax-h3-two-stage": "npm run build && node scripts/check-minimax-h3-two-stage.cjs",
@@ -108,6 +108,7 @@ import {
108
108
  usesReferenceMask
109
109
  } from './utils/index.js';
110
110
  import { getFileContentType } from './utils/fileContentType.js';
111
+ import { assertImageUploadBody } from './utils/imageUploadBody.js';
111
112
  import { imageLayersFromResult } from './utils/imageLayers.js';
112
113
  import { TokenType } from '../types/token.js';
113
114
  import type { JobProvenance } from './types/JobProvenance.js';
@@ -3315,6 +3316,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
3315
3316
  const assertSession =
3316
3317
  this._preparingSessions.get(projectId) ?? captureRequestSession(this.client.auth);
3317
3318
  assertSession();
3319
+ await assertImageUploadBody(file, 'Guide image');
3318
3320
  const contentType = getFileContentType(file);
3319
3321
  if (await this._bindSavedUpload(file, contentType, { projectId, type: 'startingImage' }))
3320
3322
  return imageId;
@@ -3349,6 +3351,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
3349
3351
  const assertSession =
3350
3352
  this._preparingSessions.get(projectId) ?? captureRequestSession(this.client.auth);
3351
3353
  assertSession();
3354
+ await assertImageUploadBody(file, 'ControlNet image');
3352
3355
  const contentType = getFileContentType(file);
3353
3356
  if (await this._bindSavedUpload(file, contentType, { projectId, type: 'cnImage' }))
3354
3357
  return imageId;
@@ -3404,6 +3407,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
3404
3407
  const assertSession =
3405
3408
  this._preparingSessions.get(projectId) ?? captureRequestSession(this.client.auth);
3406
3409
  assertSession();
3410
+ await assertImageUploadBody(file, `Context image ${imageIndex}`);
3407
3411
  const contentType = getFileContentType(file);
3408
3412
  if (
3409
3413
  await this._bindSavedUpload(file, contentType, {
@@ -3454,6 +3458,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
3454
3458
  const assertSession =
3455
3459
  this._preparingSessions.get(projectId) ?? captureRequestSession(this.client.auth);
3456
3460
  assertSession();
3461
+ await assertImageUploadBody(file, `Keyframe image ${slot}`);
3457
3462
  const contentType = getFileContentType(file);
3458
3463
  if (await this._bindSavedUpload(file, contentType, { projectId, type })) return imageId;
3459
3464
  assertSession();
@@ -3490,6 +3495,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
3490
3495
  const assertSession =
3491
3496
  this._preparingSessions.get(projectId) ?? captureRequestSession(this.client.auth);
3492
3497
  assertSession();
3498
+ await assertImageUploadBody(file, 'Reference image');
3493
3499
  const contentType = getFileContentType(file);
3494
3500
  if (await this._bindSavedUpload(file, contentType, { projectId, type: 'referenceImage' }))
3495
3501
  return imageId;
@@ -3528,6 +3534,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
3528
3534
  const assertSession =
3529
3535
  this._preparingSessions.get(projectId) ?? captureRequestSession(this.client.auth);
3530
3536
  assertSession();
3537
+ await assertImageUploadBody(file, 'Reference mask');
3531
3538
  const contentType = getFileContentType(file);
3532
3539
  if (await this._bindSavedUpload(file, contentType, { projectId, type: 'referenceMask' }))
3533
3540
  return imageId;
@@ -3566,6 +3573,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
3566
3573
  const assertSession =
3567
3574
  this._preparingSessions.get(projectId) ?? captureRequestSession(this.client.auth);
3568
3575
  assertSession();
3576
+ await assertImageUploadBody(file, 'End reference image');
3569
3577
  const contentType = getFileContentType(file);
3570
3578
  if (await this._bindSavedUpload(file, contentType, { projectId, type: 'referenceImageEnd' }))
3571
3579
  return imageId;
@@ -0,0 +1,68 @@
1
+ import { ApiError } from '../../ApiClient/index.js';
2
+
3
+ /**
4
+ * Refuse an image-slot upload whose contents are text instead of image bytes.
5
+ *
6
+ * An image slot that receives text instead of an image (a file path string,
7
+ * a fetched HTML page, or base64 text) cannot be decoded, so the job fails
8
+ * after it has been submitted. Checking the leading bytes here turns that
9
+ * into an error that names the slot, before any upload URL, upload or job
10
+ * request.
11
+ *
12
+ * Only contents that cannot be an image are refused: a `data:` URL, an HTML
13
+ * page, base64 text, or a string that is not markup. Image bytes, SVG markup
14
+ * and anything this check does not recognise upload as before.
15
+ */
16
+ export async function assertImageUploadBody(file: unknown, slot: string): Promise<void> {
17
+ const found = await describeTextBody(file);
18
+ if (!found) return;
19
+ throw new ApiError(400, {
20
+ status: 'error',
21
+ errorCode: 0,
22
+ message: `${slot} is not image data: ${found}. Upload the image file itself.`
23
+ });
24
+ }
25
+
26
+ /** Enough leading bytes for an HTML doctype after a byte-order mark and some whitespace. */
27
+ const SNIFF_BYTES = 64;
28
+ /** Sixteen base64 characters: no binary image header is made only of them. */
29
+ const BASE64_HEAD = /^[A-Za-z0-9+/]{16}/;
30
+ const DATA_URL_HEAD = /^data:/i;
31
+ /** UTF-8 byte-order mark, read either as one character or as its three bytes. */
32
+ const HTML_HEAD = /^(?:|\xef\xbb\xbf)?\s*<(?:!doctype\s+html|html[\s>])/i;
33
+ const MARKUP_HEAD = /^(?:|\xef\xbb\xbf)?\s*</;
34
+
35
+ async function describeTextBody(file: unknown): Promise<string | null> {
36
+ if (typeof file === 'string') {
37
+ const head = file.slice(0, SNIFF_BYTES);
38
+ // SVG markup is text and still uploads; any other string is not an image.
39
+ return describeHead(head) ?? (MARKUP_HEAD.test(head) ? null : 'it is a string, not the file');
40
+ }
41
+ const bytes = await leadingBytes(file);
42
+ return bytes ? describeHead(String.fromCharCode(...bytes)) : null;
43
+ }
44
+
45
+ async function leadingBytes(file: unknown): Promise<Uint8Array | null> {
46
+ try {
47
+ if (typeof Blob !== 'undefined' && file instanceof Blob) {
48
+ return new Uint8Array(await file.slice(0, SNIFF_BYTES).arrayBuffer());
49
+ }
50
+ if (ArrayBuffer.isView(file)) {
51
+ return new Uint8Array(file.buffer, file.byteOffset, Math.min(file.byteLength, SNIFF_BYTES));
52
+ }
53
+ if (file instanceof ArrayBuffer) {
54
+ return new Uint8Array(file, 0, Math.min(file.byteLength, SNIFF_BYTES));
55
+ }
56
+ } catch {
57
+ // A file that can no longer be read fails at the upload, as it always has.
58
+ }
59
+ return null;
60
+ }
61
+
62
+ function describeHead(head: string): string | null {
63
+ if (DATA_URL_HEAD.test(head)) return 'its contents are a data: URL';
64
+ if (HTML_HEAD.test(head)) return 'its contents are an HTML page';
65
+ // GIF's signature is letters and digits, and its size bytes can be too.
66
+ if (!head.startsWith('GIF8') && BASE64_HEAD.test(head)) return 'its contents are base64 text';
67
+ return null;
68
+ }
package/src/version.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED by scripts/generate-version.mjs — do not edit.
2
2
  // Source of truth: package.json version.
3
- export const LIB_VERSION = '5.84.1';
3
+ export const LIB_VERSION = '5.84.2';