@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
@@ -0,0 +1,367 @@
1
+ export type MediaType = 'image' | 'audio' | 'video';
2
+
3
+ export interface ImageDimensions {
4
+ width: number;
5
+ height: number;
6
+ }
7
+
8
+ export interface InlineMediaValidationOptions {
9
+ maxBytes?: number;
10
+ maxImageLongestSide?: number;
11
+ }
12
+
13
+ export interface ParsedInlineMediaData {
14
+ mimeType: string;
15
+ blob: Blob;
16
+ byteLength: number;
17
+ imageDimensions?: ImageDimensions;
18
+ }
19
+
20
+ type ImageFormat = 'jpeg' | 'png';
21
+ type AudioFormat = 'mpeg' | 'wav' | 'mp4';
22
+ type VideoFormat = 'mp4' | 'quicktime';
23
+
24
+ const BASE64_BODY_PATTERN = /^[A-Za-z0-9+/]*={0,2}$/;
25
+
26
+ const IMAGE_MIME_FORMATS: Record<string, ImageFormat> = {
27
+ 'image/jpeg': 'jpeg',
28
+ 'image/jpg': 'jpeg',
29
+ 'image/png': 'png'
30
+ };
31
+
32
+ const AUDIO_MIME_FORMATS: Record<string, AudioFormat> = {
33
+ 'audio/m4a': 'mp4',
34
+ 'audio/mp3': 'mpeg',
35
+ 'audio/mp4': 'mp4',
36
+ 'audio/mpeg': 'mpeg',
37
+ 'audio/wav': 'wav',
38
+ 'audio/wave': 'wav',
39
+ 'audio/x-m4a': 'mp4',
40
+ 'audio/x-wav': 'wav'
41
+ };
42
+
43
+ const VIDEO_MIME_FORMATS: Record<string, VideoFormat> = {
44
+ 'video/mp4': 'mp4',
45
+ 'video/quicktime': 'quicktime'
46
+ };
47
+
48
+ function getAllowedMimeTypes(mediaType: MediaType): string[] {
49
+ switch (mediaType) {
50
+ case 'image':
51
+ return Object.keys(IMAGE_MIME_FORMATS);
52
+ case 'audio':
53
+ return Object.keys(AUDIO_MIME_FORMATS);
54
+ case 'video':
55
+ return Object.keys(VIDEO_MIME_FORMATS);
56
+ default:
57
+ return [];
58
+ }
59
+ }
60
+
61
+ function ascii(bytes: Uint8Array, start: number, length: number): string {
62
+ if (start + length > bytes.length) {
63
+ return '';
64
+ }
65
+ return String.fromCharCode(...bytes.slice(start, start + length));
66
+ }
67
+
68
+ function hasPrefix(bytes: Uint8Array, prefix: number[], offset = 0): boolean {
69
+ if (offset + prefix.length > bytes.length) {
70
+ return false;
71
+ }
72
+ return prefix.every((value, index) => bytes[offset + index] === value);
73
+ }
74
+
75
+ function addBase64Padding(base64: string): string {
76
+ const remainder = base64.length % 4;
77
+ if (remainder === 0) return base64;
78
+ if (remainder === 1) {
79
+ throw new Error('Invalid base64 payload');
80
+ }
81
+ return base64.padEnd(base64.length + (4 - remainder), '=');
82
+ }
83
+
84
+ function encodeBase64(bytes: Uint8Array): string {
85
+ if (typeof Buffer !== 'undefined') {
86
+ return Buffer.from(bytes).toString('base64');
87
+ }
88
+
89
+ let binary = '';
90
+ for (const byte of bytes) {
91
+ binary += String.fromCharCode(byte);
92
+ }
93
+
94
+ if (typeof btoa === 'function') {
95
+ return btoa(binary);
96
+ }
97
+
98
+ throw new Error('No base64 encoder available in this environment');
99
+ }
100
+
101
+ function decodeStrictBase64(base64: string): Uint8Array {
102
+ const sanitized = base64.replace(/\s+/g, '');
103
+ if (!BASE64_BODY_PATTERN.test(sanitized)) {
104
+ throw new Error('Invalid base64 payload');
105
+ }
106
+
107
+ const padded = addBase64Padding(sanitized);
108
+ let bytes: Uint8Array;
109
+
110
+ if (typeof Buffer !== 'undefined') {
111
+ bytes = Uint8Array.from(Buffer.from(padded, 'base64'));
112
+ } else if (typeof atob === 'function') {
113
+ const binaryString = atob(padded);
114
+ bytes = new Uint8Array(binaryString.length);
115
+ for (let i = 0; i < binaryString.length; i++) {
116
+ bytes[i] = binaryString.charCodeAt(i);
117
+ }
118
+ } else {
119
+ throw new Error('No base64 decoder available in this environment');
120
+ }
121
+
122
+ if (bytes.length === 0) {
123
+ throw new Error('Invalid base64 payload');
124
+ }
125
+
126
+ if (encodeBase64(bytes) !== padded) {
127
+ throw new Error('Invalid base64 payload');
128
+ }
129
+
130
+ return bytes;
131
+ }
132
+
133
+ function detectImageFormat(bytes: Uint8Array): ImageFormat | null {
134
+ if (hasPrefix(bytes, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {
135
+ return 'png';
136
+ }
137
+ if (hasPrefix(bytes, [0xff, 0xd8, 0xff])) {
138
+ return 'jpeg';
139
+ }
140
+ return null;
141
+ }
142
+
143
+ function detectIsoBmffFormat(bytes: Uint8Array): 'mp4' | 'quicktime' | null {
144
+ if (bytes.length < 12 || ascii(bytes, 4, 4) !== 'ftyp') {
145
+ return null;
146
+ }
147
+
148
+ const majorBrand = ascii(bytes, 8, 4);
149
+ if (majorBrand === 'qt ') {
150
+ return 'quicktime';
151
+ }
152
+ return 'mp4';
153
+ }
154
+
155
+ function isLikelyMp3Frame(bytes: Uint8Array): boolean {
156
+ if (bytes.length < 2) {
157
+ return false;
158
+ }
159
+ const b0 = bytes[0];
160
+ const b1 = bytes[1];
161
+ const versionBits = (b1 >> 3) & 0x03;
162
+ const layerBits = (b1 >> 1) & 0x03;
163
+ return b0 === 0xff && (b1 & 0xe0) === 0xe0 && versionBits !== 0x01 && layerBits !== 0x00;
164
+ }
165
+
166
+ function detectAudioFormat(bytes: Uint8Array): AudioFormat | null {
167
+ if (ascii(bytes, 0, 4) === 'RIFF' && ascii(bytes, 8, 4) === 'WAVE') {
168
+ return 'wav';
169
+ }
170
+ if (ascii(bytes, 0, 3) === 'ID3' || isLikelyMp3Frame(bytes)) {
171
+ return 'mpeg';
172
+ }
173
+
174
+ const isoFormat = detectIsoBmffFormat(bytes);
175
+ if (isoFormat === 'mp4') {
176
+ return 'mp4';
177
+ }
178
+
179
+ return null;
180
+ }
181
+
182
+ function detectVideoFormat(bytes: Uint8Array): VideoFormat | null {
183
+ const isoFormat = detectIsoBmffFormat(bytes);
184
+ if (isoFormat === 'quicktime') {
185
+ return 'quicktime';
186
+ }
187
+ if (isoFormat === 'mp4') {
188
+ return 'mp4';
189
+ }
190
+ return null;
191
+ }
192
+
193
+ function parsePngDimensions(bytes: Uint8Array): ImageDimensions | null {
194
+ if (bytes.length < 24 || ascii(bytes, 12, 4) !== 'IHDR') {
195
+ return null;
196
+ }
197
+ const width = (bytes[16] << 24) | (bytes[17] << 16) | (bytes[18] << 8) | bytes[19];
198
+ const height = (bytes[20] << 24) | (bytes[21] << 16) | (bytes[22] << 8) | bytes[23];
199
+ if (width <= 0 || height <= 0) {
200
+ return null;
201
+ }
202
+ return { width, height };
203
+ }
204
+
205
+ function parseJpegDimensions(bytes: Uint8Array): ImageDimensions | null {
206
+ let offset = 2;
207
+
208
+ while (offset + 1 < bytes.length) {
209
+ while (offset < bytes.length && bytes[offset] !== 0xff) {
210
+ offset += 1;
211
+ }
212
+ while (offset < bytes.length && bytes[offset] === 0xff) {
213
+ offset += 1;
214
+ }
215
+ if (offset >= bytes.length) {
216
+ break;
217
+ }
218
+
219
+ const marker = bytes[offset];
220
+ offset += 1;
221
+
222
+ if (
223
+ marker === 0xd8 ||
224
+ marker === 0xd9 ||
225
+ (marker >= 0xd0 && marker <= 0xd7) ||
226
+ marker === 0x01
227
+ ) {
228
+ continue;
229
+ }
230
+
231
+ if (offset + 1 >= bytes.length) {
232
+ break;
233
+ }
234
+
235
+ const segmentLength = (bytes[offset] << 8) | bytes[offset + 1];
236
+ if (segmentLength < 2 || offset + segmentLength > bytes.length) {
237
+ break;
238
+ }
239
+
240
+ const isStartOfFrame =
241
+ marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc;
242
+
243
+ if (isStartOfFrame) {
244
+ if (offset + 6 >= bytes.length) {
245
+ break;
246
+ }
247
+ const height = (bytes[offset + 3] << 8) | bytes[offset + 4];
248
+ const width = (bytes[offset + 5] << 8) | bytes[offset + 6];
249
+ if (width > 0 && height > 0) {
250
+ return { width, height };
251
+ }
252
+ return null;
253
+ }
254
+
255
+ offset += segmentLength;
256
+ }
257
+
258
+ return null;
259
+ }
260
+
261
+ function parseImageDimensions(bytes: Uint8Array, format: ImageFormat): ImageDimensions | null {
262
+ switch (format) {
263
+ case 'png':
264
+ return parsePngDimensions(bytes);
265
+ case 'jpeg':
266
+ return parseJpegDimensions(bytes);
267
+ default:
268
+ return null;
269
+ }
270
+ }
271
+
272
+ function validateMagicBytes(
273
+ mediaType: MediaType,
274
+ mimeType: string,
275
+ bytes: Uint8Array
276
+ ): ImageDimensions | undefined {
277
+ if (mediaType === 'image') {
278
+ const expectedFormat = IMAGE_MIME_FORMATS[mimeType];
279
+ if (!expectedFormat) {
280
+ throw new Error(
281
+ `Unsupported inline image MIME type ${mimeType}. Allowed types: ${getAllowedMimeTypes('image').join(', ')}`
282
+ );
283
+ }
284
+ const detectedFormat = detectImageFormat(bytes);
285
+ if (detectedFormat !== expectedFormat) {
286
+ throw new Error(`Inline image data does not match declared MIME type ${mimeType}`);
287
+ }
288
+
289
+ const dimensions = parseImageDimensions(bytes, detectedFormat);
290
+ if (!dimensions) {
291
+ throw new Error('Unable to determine inline image dimensions');
292
+ }
293
+ return dimensions;
294
+ }
295
+
296
+ if (mediaType === 'audio') {
297
+ const expectedFormat = AUDIO_MIME_FORMATS[mimeType];
298
+ if (!expectedFormat) {
299
+ throw new Error(
300
+ `Unsupported inline audio MIME type ${mimeType}. Allowed types: ${getAllowedMimeTypes('audio').join(', ')}`
301
+ );
302
+ }
303
+ const detectedFormat = detectAudioFormat(bytes);
304
+ if (detectedFormat !== expectedFormat) {
305
+ throw new Error(`Inline audio data does not match declared MIME type ${mimeType}`);
306
+ }
307
+ return undefined;
308
+ }
309
+
310
+ const expectedFormat = VIDEO_MIME_FORMATS[mimeType];
311
+ if (!expectedFormat) {
312
+ throw new Error(
313
+ `Unsupported inline video MIME type ${mimeType}. Allowed types: ${getAllowedMimeTypes('video').join(', ')}`
314
+ );
315
+ }
316
+ const detectedFormat = detectVideoFormat(bytes);
317
+ if (detectedFormat !== expectedFormat) {
318
+ throw new Error(`Inline video data does not match declared MIME type ${mimeType}`);
319
+ }
320
+ return undefined;
321
+ }
322
+
323
+ function validateImageDimensions(dimensions: ImageDimensions, maxLongestSide: number): void {
324
+ if (Math.max(dimensions.width, dimensions.height) > maxLongestSide) {
325
+ throw new Error(
326
+ `Inline image exceeds maximum dimensions of ${maxLongestSide}px on its longest side`
327
+ );
328
+ }
329
+ }
330
+
331
+ export function parseInlineMediaDataUri(
332
+ input: string,
333
+ mediaType: MediaType,
334
+ options: InlineMediaValidationOptions = {}
335
+ ): ParsedInlineMediaData {
336
+ const trimmed = input.trim();
337
+ const match = /^data:([^;,]+);base64,([A-Za-z0-9+/=\s]+)$/i.exec(trimmed);
338
+ if (!match) {
339
+ throw new Error(
340
+ `Only inline base64-encoded data URIs are supported for ${mediaType} inputs; remote URLs are not allowed`
341
+ );
342
+ }
343
+
344
+ const mimeType = match[1].toLowerCase();
345
+ const bytes = decodeStrictBase64(match[2]);
346
+
347
+ if (options.maxBytes !== undefined && bytes.length > options.maxBytes) {
348
+ throw new Error(
349
+ `${mediaType} input exceeds ${Math.round(options.maxBytes / (1024 * 1024))}MB limit`
350
+ );
351
+ }
352
+
353
+ const imageDimensions = validateMagicBytes(mediaType, mimeType, bytes);
354
+ if (imageDimensions && options.maxImageLongestSide !== undefined) {
355
+ validateImageDimensions(imageDimensions, options.maxImageLongestSide);
356
+ }
357
+
358
+ const blobBytes = new Uint8Array(bytes.length);
359
+ blobBytes.set(bytes);
360
+
361
+ return {
362
+ mimeType,
363
+ blob: new Blob([blobBytes], { type: mimeType }),
364
+ byteLength: bytes.length,
365
+ imageDimensions
366
+ };
367
+ }
@@ -1,8 +1,40 @@
1
1
  import { ApiError } from '../ApiClient';
2
2
  import { ModelOptions } from '../Projects/types/ModelOptions';
3
3
 
4
- export function validateCustomImageSize(value: any): number {
5
- return validateNumber(value, { min: 256, max: 2048, propertyName: 'Width and height' });
4
+ const EXTENDED_IMAGE_SIZE_MODEL_IDS = new Set([
5
+ 'z_image_bf16',
6
+ 'z_image_turbo_bf16',
7
+ 'qwen_image_edit_2511_fp8',
8
+ 'qwen_image_edit_2511_fp8_lightning',
9
+ 'qwen_image_2512_fp8',
10
+ 'qwen_image_2512_fp8_lightning',
11
+ 'flux2_dev_fp8'
12
+ ]);
13
+
14
+ interface ImageSizeValidationOptions {
15
+ modelId?: string;
16
+ propertyName?: string;
17
+ }
18
+
19
+ function getMaxCustomImageSize(modelId?: string): number {
20
+ if (modelId === 'gpt-image-2') {
21
+ return 3840;
22
+ }
23
+ if (modelId && EXTENDED_IMAGE_SIZE_MODEL_IDS.has(modelId)) {
24
+ return 2560;
25
+ }
26
+ return 2048;
27
+ }
28
+
29
+ export function validateCustomImageSize(
30
+ value: any,
31
+ { modelId, propertyName = 'Width and height' }: ImageSizeValidationOptions = {}
32
+ ): number {
33
+ return validateNumber(value, {
34
+ min: 256,
35
+ max: getMaxCustomImageSize(modelId),
36
+ propertyName
37
+ });
6
38
  }
7
39
 
8
40
  /**
@@ -79,12 +111,16 @@ export function isComfyModel(modelId: string): boolean {
79
111
 
80
112
  /**
81
113
  * Get the maximum number of context images supported by a model.
114
+ * - GPT Image 2: 16 images
82
115
  * - Flux.2 Dev: 6 images
83
116
  * - Qwen Image Edit: 3 images
84
117
  * - Flux Kontext: 2 images
85
118
  * - Default: 3 images
86
119
  */
87
120
  export function getMaxContextImages(modelId: string): number {
121
+ if (modelId === 'gpt-image-2') {
122
+ return 16;
123
+ }
88
124
  if (modelId.startsWith('flux2_')) {
89
125
  return 6;
90
126
  }