@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.
- package/CHANGELOG.md +7 -0
- package/dist/Projects/index.js +8 -0
- package/dist/Projects/index.js.map +1 -1
- package/dist/Projects/utils/imageUploadBody.d.ts +14 -0
- package/dist/Projects/utils/imageUploadBody.js +88 -0
- package/dist/Projects/utils/imageUploadBody.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist-esm/Projects/index.js +8 -0
- package/dist-esm/Projects/index.js.map +1 -1
- package/dist-esm/Projects/utils/imageUploadBody.js +85 -0
- package/dist-esm/Projects/utils/imageUploadBody.js.map +1 -0
- package/dist-esm/version.js +1 -1
- package/package.json +2 -1
- package/src/Projects/index.ts +8 -0
- package/src/Projects/utils/imageUploadBody.ts +68 -0
- package/src/version.ts +1 -1
|
@@ -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"}
|
package/dist-esm/version.js
CHANGED
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "5.84.
|
|
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",
|
package/src/Projects/index.ts
CHANGED
|
@@ -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