@sogni-ai/sogni-client 5.63.2 → 5.63.3

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,60 @@
1
+ /**
2
+ * Detect the content type of a reference file from its bytes.
3
+ *
4
+ * A browser `File`/`Blob` carries its own `type`. A Node `Buffer` is sniffed
5
+ * by magic bytes. `slot` says which reference the buffer fills: a
6
+ * Matroska/WebM container (EBML header) and an Ogg container carry either
7
+ * audio or video, and the magic bytes do not say which, so the slot decides
8
+ * between `audio/webm` and `video/webm` (`audio/ogg` and `video/ogg`).
9
+ *
10
+ * The declared type matters past the upload: the socket keeps audio guides the
11
+ * Mac app's AVFoundation decoder cannot read (OGG, WebM) off Mac app workers
12
+ * only when the type is declared; an undeclared guide is routed blind and the
13
+ * render fails there (sogni-socket constants/appMlxWorker.js
14
+ * APP_MLX_AUDIO_GUIDE_CONTENT_TYPES, 2026-10).
15
+ *
16
+ * Returns undefined when the bytes match nothing known.
17
+ */
18
+ export function getFileContentType(file, slot) {
19
+ if (file instanceof Blob && 'type' in file && file.type) {
20
+ return file.type;
21
+ }
22
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(file)) {
23
+ if (file.length >= 12) {
24
+ if (file[0] === 0xff && file[1] === 0xd8 && file[2] === 0xff)
25
+ return 'image/jpeg';
26
+ if (file[0] === 0x89 && file[1] === 0x50 && file[2] === 0x4e && file[3] === 0x47) {
27
+ return 'image/png';
28
+ }
29
+ if (file.toString('ascii', 0, 4) === 'RIFF' && file.toString('ascii', 8, 12) === 'WEBP') {
30
+ return 'image/webp';
31
+ }
32
+ if (file.toString('ascii', 0, 3) === 'GIF')
33
+ return 'image/gif';
34
+ if (file.toString('ascii', 4, 8) === 'ftyp') {
35
+ const brand = file.toString('ascii', 8, 12).toLowerCase();
36
+ if (brand.includes('m4a') || brand.includes('m4b'))
37
+ return 'audio/mp4';
38
+ if (brand.includes('qt'))
39
+ return 'video/quicktime';
40
+ return 'video/mp4';
41
+ }
42
+ if (file.toString('ascii', 0, 4) === 'RIFF' && file.toString('ascii', 8, 12) === 'WAVE') {
43
+ return 'audio/wav';
44
+ }
45
+ if (file.toString('ascii', 0, 4) === 'fLaC')
46
+ return 'audio/flac';
47
+ if (file.toString('ascii', 0, 4) === 'OggS')
48
+ return slot === 'video' ? 'video/ogg' : 'audio/ogg';
49
+ if (file[0] === 0x1a && file[1] === 0x45 && file[2] === 0xdf && file[3] === 0xa3) {
50
+ return slot === 'audio' ? 'audio/webm' : 'video/webm';
51
+ }
52
+ }
53
+ if (file.length >= 3 && file.toString('ascii', 0, 3) === 'ID3')
54
+ return 'audio/mpeg';
55
+ if (file.length >= 2 && file[0] === 0xff && (file[1] & 0xe0) === 0xe0)
56
+ return 'audio/mpeg';
57
+ }
58
+ return undefined;
59
+ }
60
+ //# sourceMappingURL=fileContentType.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileContentType.js","sourceRoot":"","sources":["../../../src/Projects/utils/fileContentType.ts"],"names":[],"mappings":"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"}
@@ -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.63.2';
3
+ export const LIB_VERSION = '5.63.3';
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.63.2",
6
+ "version": "5.63.3",
7
7
  "engines": {
8
8
  "node": ">=22"
9
9
  },
@@ -80,6 +80,7 @@
80
80
  "test:capability-exports": "npm run build && node scripts/check-capability-exports.cjs",
81
81
  "test:generation-receipt-transport": "npm run build && node scripts/check-generation-receipt-transport.cjs",
82
82
  "test:prompt-expansion": "npm run build && node scripts/check-prompt-expansion-transport.cjs",
83
+ "test:reference-content-types": "npm run build && node scripts/check-reference-content-types.cjs",
83
84
  "test:minimax-h3-audio-policy": "node scripts/check-minimax-h3-source-audio-policy.cjs",
84
85
  "test:minimax-h3-balanced": "npm run build && node scripts/check-minimax-h3-balanced.cjs",
85
86
  "test:minimax-h3-two-stage": "npm run build && node scripts/check-minimax-h3-two-stage.cjs",
@@ -88,6 +88,7 @@ import {
88
88
  type ResultMediaKind,
89
89
  usesReferenceMask
90
90
  } from './utils/index.js';
91
+ import { getFileContentType } from './utils/fileContentType.js';
91
92
  import { TokenType } from '../types/token.js';
92
93
  import type { JobProvenance } from './types/JobProvenance.js';
93
94
  import { getMaxContextImages, validateSampler, isGptImageModel } from '../lib/validation.js';
@@ -249,41 +250,6 @@ function deriveLoraCapableModelIds(loras: LoraCatalogEntry[]): string[] {
249
250
  return Array.from(modelIds).sort();
250
251
  }
251
252
 
252
- /**
253
- * Detect content type from a file object.
254
- * For File objects in browser, uses the type property.
255
- * Returns undefined if content type cannot be detected.
256
- */
257
- function getFileContentType(file: File | Buffer | Blob): string | undefined {
258
- if (file instanceof Blob && 'type' in file && file.type) {
259
- return file.type;
260
- }
261
- if (typeof Buffer !== 'undefined' && Buffer.isBuffer(file)) {
262
- if (file.length >= 12) {
263
- if (file[0] === 0xff && file[1] === 0xd8 && file[2] === 0xff) return 'image/jpeg';
264
- if (file[0] === 0x89 && file[1] === 0x50 && file[2] === 0x4e && file[3] === 0x47) {
265
- return 'image/png';
266
- }
267
- if (file.toString('ascii', 0, 4) === 'RIFF' && file.toString('ascii', 8, 12) === 'WEBP') {
268
- return 'image/webp';
269
- }
270
- if (file.toString('ascii', 0, 3) === 'GIF') return 'image/gif';
271
- if (file.toString('ascii', 4, 8) === 'ftyp') {
272
- const brand = file.toString('ascii', 8, 12).toLowerCase();
273
- if (brand.includes('m4a') || brand.includes('m4b')) return 'audio/mp4';
274
- if (brand.includes('qt')) return 'video/quicktime';
275
- return 'video/mp4';
276
- }
277
- if (file.toString('ascii', 0, 4) === 'RIFF' && file.toString('ascii', 8, 12) === 'WAVE') {
278
- return 'audio/wav';
279
- }
280
- }
281
- if (file.length >= 3 && file.toString('ascii', 0, 3) === 'ID3') return 'audio/mpeg';
282
- if (file.length >= 2 && file[0] === 0xff && (file[1] & 0xe0) === 0xe0) return 'audio/mpeg';
283
- }
284
- return undefined;
285
- }
286
-
287
253
  /**
288
254
  * Convert file to a format compatible with fetch body.
289
255
  * Converts Node.js Buffer to Blob for cross-platform compatibility.
@@ -2473,7 +2439,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
2473
2439
  const keyFrame = request.keyFrames?.[0];
2474
2440
  if (!keyFrame) return;
2475
2441
  if (data.referenceAudio && data.referenceAudio !== true) {
2476
- keyFrame.referenceAudioContentType = getFileContentType(data.referenceAudio);
2442
+ keyFrame.referenceAudioContentType = getFileContentType(data.referenceAudio, 'audio');
2477
2443
  }
2478
2444
  }
2479
2445
 
@@ -2548,25 +2514,25 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
2548
2514
  }
2549
2515
  const isMinimaxH3R2v = isMinimaxH3ReferenceModel(data.modelId);
2550
2516
  if (!isMinimaxH3R2v && data.referenceAudio && data.referenceAudio !== true) {
2551
- keyFrame.referenceAudioContentType = getFileContentType(data.referenceAudio);
2517
+ keyFrame.referenceAudioContentType = getFileContentType(data.referenceAudio, 'audio');
2552
2518
  }
2553
2519
  if (data.referenceAudioIdentity && data.referenceAudioIdentity !== true) {
2554
- const contentType = getFileContentType(data.referenceAudioIdentity);
2520
+ const contentType = getFileContentType(data.referenceAudioIdentity, 'audio');
2555
2521
  keyFrame.referenceAudioIdentityContentType = contentType;
2556
2522
  keyFrame.referenceAudioContentType ??= contentType;
2557
2523
  }
2558
2524
  if (!isMinimaxH3R2v && data.referenceVideo && data.referenceVideo !== true) {
2559
- keyFrame.referenceVideoContentType = getFileContentType(data.referenceVideo);
2525
+ keyFrame.referenceVideoContentType = getFileContentType(data.referenceVideo, 'video');
2560
2526
  }
2561
2527
  if (isMinimaxH3R2v) {
2562
2528
  for (const { slot, media } of getMinimaxH3ReferenceAudioSlots(data)) {
2563
2529
  if (typeof media !== 'boolean') {
2564
- keyFrame[`referenceAudio${slot}ContentType`] = getFileContentType(media);
2530
+ keyFrame[`referenceAudio${slot}ContentType`] = getFileContentType(media, 'audio');
2565
2531
  }
2566
2532
  }
2567
2533
  for (const { slot, media } of getMinimaxH3ReferenceVideoSlots(data)) {
2568
2534
  if (typeof media !== 'boolean') {
2569
- keyFrame[`referenceVideo${slot}ContentType`] = getFileContentType(media);
2535
+ keyFrame[`referenceVideo${slot}ContentType`] = getFileContentType(media, 'video');
2570
2536
  }
2571
2537
  }
2572
2538
  }
@@ -3233,7 +3199,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
3233
3199
  const assertSession =
3234
3200
  this._preparingSessions.get(projectId) ?? captureRequestSession(this.client.auth);
3235
3201
  assertSession();
3236
- const contentType = getFileContentType(file);
3202
+ const contentType = getFileContentType(file, 'audio');
3237
3203
  if (await this.assets.tryBindFile(file, contentType, { projectId, type: 'referenceAudio', id }))
3238
3204
  return;
3239
3205
  assertSession();
@@ -3272,7 +3238,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
3272
3238
  const assertSession =
3273
3239
  this._preparingSessions.get(projectId) ?? captureRequestSession(this.client.auth);
3274
3240
  assertSession();
3275
- const contentType = getFileContentType(file);
3241
+ const contentType = getFileContentType(file, 'video');
3276
3242
  if (await this.assets.tryBindFile(file, contentType, { projectId, type: 'referenceVideo', id }))
3277
3243
  return;
3278
3244
  assertSession();
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Detect the content type of a reference file from its bytes.
3
+ *
4
+ * A browser `File`/`Blob` carries its own `type`. A Node `Buffer` is sniffed
5
+ * by magic bytes. `slot` says which reference the buffer fills: a
6
+ * Matroska/WebM container (EBML header) and an Ogg container carry either
7
+ * audio or video, and the magic bytes do not say which, so the slot decides
8
+ * between `audio/webm` and `video/webm` (`audio/ogg` and `video/ogg`).
9
+ *
10
+ * The declared type matters past the upload: the socket keeps audio guides the
11
+ * Mac app's AVFoundation decoder cannot read (OGG, WebM) off Mac app workers
12
+ * only when the type is declared; an undeclared guide is routed blind and the
13
+ * render fails there (sogni-socket constants/appMlxWorker.js
14
+ * APP_MLX_AUDIO_GUIDE_CONTENT_TYPES, 2026-10).
15
+ *
16
+ * Returns undefined when the bytes match nothing known.
17
+ */
18
+ export function getFileContentType(
19
+ file: File | Buffer | Blob,
20
+ slot?: 'audio' | 'video'
21
+ ): string | undefined {
22
+ if (file instanceof Blob && 'type' in file && file.type) {
23
+ return file.type;
24
+ }
25
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(file)) {
26
+ if (file.length >= 12) {
27
+ if (file[0] === 0xff && file[1] === 0xd8 && file[2] === 0xff) return 'image/jpeg';
28
+ if (file[0] === 0x89 && file[1] === 0x50 && file[2] === 0x4e && file[3] === 0x47) {
29
+ return 'image/png';
30
+ }
31
+ if (file.toString('ascii', 0, 4) === 'RIFF' && file.toString('ascii', 8, 12) === 'WEBP') {
32
+ return 'image/webp';
33
+ }
34
+ if (file.toString('ascii', 0, 3) === 'GIF') return 'image/gif';
35
+ if (file.toString('ascii', 4, 8) === 'ftyp') {
36
+ const brand = file.toString('ascii', 8, 12).toLowerCase();
37
+ if (brand.includes('m4a') || brand.includes('m4b')) return 'audio/mp4';
38
+ if (brand.includes('qt')) return 'video/quicktime';
39
+ return 'video/mp4';
40
+ }
41
+ if (file.toString('ascii', 0, 4) === 'RIFF' && file.toString('ascii', 8, 12) === 'WAVE') {
42
+ return 'audio/wav';
43
+ }
44
+ if (file.toString('ascii', 0, 4) === 'fLaC') return 'audio/flac';
45
+ if (file.toString('ascii', 0, 4) === 'OggS')
46
+ return slot === 'video' ? 'video/ogg' : 'audio/ogg';
47
+ if (file[0] === 0x1a && file[1] === 0x45 && file[2] === 0xdf && file[3] === 0xa3) {
48
+ return slot === 'audio' ? 'audio/webm' : 'video/webm';
49
+ }
50
+ }
51
+ if (file.length >= 3 && file.toString('ascii', 0, 3) === 'ID3') return 'audio/mpeg';
52
+ if (file.length >= 2 && file[0] === 0xff && (file[1] & 0xe0) === 0xe0) return 'audio/mpeg';
53
+ }
54
+ return undefined;
55
+ }
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.63.2';
3
+ export const LIB_VERSION = '5.63.3';