@sogni-ai/sogni-client 5.84.0 → 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.0';
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.0",
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",
@@ -158,7 +159,7 @@
158
159
  "homepage": "https://github.com/Sogni-AI/sogni-client#readme",
159
160
  "dependencies": {
160
161
  "@simplewebauthn/browser": "14.0.0",
161
- "@sogni-ai/sogni-protocol": "1.0.0-alpha.62",
162
+ "@sogni-ai/sogni-protocol": "1.0.0-alpha.63",
162
163
  "@types/js-cookie": "^3.0.6",
163
164
  "@types/lodash": "^4.17.23",
164
165
  "@types/ws": "^8.5.13",
@@ -806,7 +806,7 @@ export const SOGNI_HOSTED_TOOLS_MANIFEST: unknown = {
806
806
  "type": "function",
807
807
  "function": {
808
808
  "name": "upscale_video",
809
- "description": "Upscale an existing video to 1080p, 1440p or 2160p (4K) with recommended RelayVSR, or to 1080p or 1440p with FlashVSR. This is promptless, deterministic enhancement, not a generative edit: it keeps every source frame, the exact frame rate, the full aspect ratio, and the original audio, and it never changes content, trims, crops, restyles, or interpolates frames. Use it when the user asks to upscale, enlarge, sharpen, or increase the resolution of an uploaded or previously generated video, or wants an HD, 1080p, 1440p, 2K, or 4K copy of it. Do not use it to create, restyle, extend, or edit a video; when the user explicitly asks a generative model such as Seedance to re-render the clip, use video_to_video. Explicitly set model to relayvsr for new upscales. Use flashvsr when the user requests FlashVSR or specifically wants its detailPreference, processingSpeed or seed controls. Omitted model retains FlashVSR for compatibility. With FlashVSR the output is at most twice the source size, so 1080p needs a source whose short edge is 540-768px and 1440p needs 720-768px, and the source can be at most about 1344x768 pixels (768x1344 in portrait). With RelayVSR the output is at most twice the source size for a source up to 1920x1080 pixels (1080x1920 in portrait), or four times for a source up to 1344x768 (768x1344 in portrait), so 2160p needs a source whose short edge is 540-768px or exactly 1080px, and the output's long edge can be at most 3840px. The source must also be 1-60 fps and at most 100 MB. The server sets the maximum clip length and rejects a source that is too long; never quote a length limit yourself, and if the tool reports that rejection, relay that error to the user. FlashVSR cannot produce 4K or any size above 1440p, so a 4K request always uses relayvsr. Nothing here produces a size above 2160p; when the user asks for one, say so and offer 2160p. Each upscale costs credits based on the source video's size and length. With FlashVSR, optional detailPreference (stable or sharper), processingSpeed (stable or faster), and seed tune the result; omit them unless the user asks, and none of them changes the price. RelayVSR refuses all three.",
809
+ "description": "Upscale an existing video to 1080p, 1440p or 2160p (4K) with recommended RelayVSR, or to 1080p or 1440p with FlashVSR. This is promptless, deterministic enhancement, not a generative edit: it keeps every source frame, the exact frame rate, the full aspect ratio, and the original audio, and it never changes content, trims, crops, restyles, or interpolates frames. Use it when the user asks to upscale, enlarge, sharpen, or increase the resolution of an uploaded or previously generated video, or wants an HD, 1080p, 1440p, 2K, or 4K copy of it. Do not use it to create, restyle, extend, or edit a video; when the user explicitly asks a generative model such as Seedance to re-render the clip, use video_to_video. Explicitly set model to relayvsr for new upscales. Use flashvsr when the user requests FlashVSR or specifically wants its detailPreference, processingSpeed or seed controls. Omitted model retains FlashVSR for compatibility. With FlashVSR the output is at most twice the source size, so 1080p needs a source whose short edge is 540-768px and 1440p needs 720-768px, and the source can be at most about 1344x768 pixels (768x1344 in portrait). With RelayVSR the output is at most twice the source size for a source up to 1920x1088 pixels (1088x1920 in portrait), or four times for a source up to 1344x768 (768x1344 in portrait), so 2160p needs a source whose short edge is 540-768px or 1080-1088px, and the output's long edge can be at most 3840px. The source must also be 1-60 fps and at most 100 MB. The server sets the maximum clip length and rejects a source that is too long; never quote a length limit yourself, and if the tool reports that rejection, relay that error to the user. FlashVSR cannot produce 4K or any size above 1440p, so a 4K request always uses relayvsr. Nothing here produces a size above 2160p; when the user asks for one, say so and offer 2160p. Each upscale costs credits based on the source video's size and length. With FlashVSR, optional detailPreference (stable or sharper), processingSpeed (stable or faster), and seed tune the result; omit them unless the user asks, and none of them changes the price. RelayVSR refuses all three.",
810
810
  "parameters": {
811
811
  "type": "object",
812
812
  "properties": {
@@ -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.0';
3
+ export const LIB_VERSION = '5.84.2';