2dai-cloud-sdk 1.4.1

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/README.md ADDED
@@ -0,0 +1,1289 @@
1
+ # 2dai-cloud-sdk
2
+
3
+ Official TypeScript/JavaScript SDK for 2DAI.io Cloud AI Generation API
4
+
5
+ Generate images, videos, and text using state-of-the-art AI models through a simple and intuitive API.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/2dai-cloud-sdk.svg)](https://www.npmjs.com/package/2dai-cloud-sdk)
8
+ [![npm downloads](https://img.shields.io/npm/dm/2dai-cloud-sdk.svg)](https://www.npmjs.com/package/2dai-cloud-sdk)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue)](https://www.typescriptlang.org/)
10
+ [![Node](https://img.shields.io/badge/node-%3E%3D18.0.0-green)](https://nodejs.org/)
11
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
12
+
13
+ ---
14
+
15
+ ## Table of Contents
16
+
17
+ - [Features](#features)
18
+ - [What's New](#whats-new)
19
+ - [Installation](#installation)
20
+ - [Getting an API Key](#getting-an-api-key)
21
+ - [Quick Start](#quick-start)
22
+ - [Documentation](#documentation)
23
+ - [Initialization](#initialization)
24
+ - [Image Generation](#image-generation)
25
+ - [Image Editing](#image-editing)
26
+ - [Image Upscaling](#image-upscaling)
27
+ - [Video Generation](#video-generation)
28
+ - [Text Generation (LLM)](#text-generation-llm)
29
+ - [CDN Operations](#cdn-operations)
30
+ - [Watermarking](#watermarking)
31
+ - [Settings & Usage Tracking](#settings--usage-tracking)
32
+ - [Advanced Usage](#advanced-usage)
33
+ - [Complete Examples](#complete-examples)
34
+ - [API Reference](#api-reference)
35
+ - [Best Practices](#best-practices)
36
+ - [Troubleshooting](#troubleshooting)
37
+ - [Migration Guide](#migration-guide)
38
+
39
+ ---
40
+
41
+ ## Features
42
+
43
+ - **Text-to-Image**: Generate stunning images from text prompts
44
+ - **Image-to-Image**: Edit and transform existing images
45
+ - **AI Image Upscale**: Upscale images 2-4x using AI
46
+ - **Image-to-Video**: Create videos from static images
47
+ - **LLM Text Generation**: Generate text with context, memory, and JSON support
48
+ - **Image Description (Vision)**: Analyze images with LLM for structured data extraction
49
+ - **12 Style Presets**: Realistic, anime, manga, cinematic, and more
50
+ - **7 Format Presets**: Portrait, landscape, profile, story, post, smartphone, banner
51
+ - **Type-Safe**: Full TypeScript support with comprehensive type definitions
52
+ - **Built-in Watermarking**: Apply custom watermarks to generated content
53
+ - **WebSocket Support**: Real-time generation with progress updates
54
+ - **Usage Tracking**: Real-time usage monitoring with remaining quota and reset times
55
+ - **Rate Limiting**: Built-in rate limit tracking for requests and tokens (LLM)
56
+ - **CDN Format Conversion**: Convert between image formats (JPG, PNG, GIF) and extract frames from videos
57
+ - **Comprehensive Tests**: Full test coverage for all API endpoints and WebSocket operations
58
+
59
+ ---
60
+
61
+ ## What's New
62
+
63
+ ### v1.4.0 (Latest)
64
+
65
+ - **AI Image Upscale** - New upscale methods for AI-powered image upscaling
66
+ - `upscaleImage()` - REST API upscaling with factor 2-4x
67
+ - `wsUpscaleImage()` - WebSocket upscaling for real-time operations
68
+ - **Image Description (Vision)** - Analyze images with LLM
69
+ - Pass `imageId` to `generateText()` or `wsGenerateLlm()` for image analysis
70
+ - Extract structured JSON data from images
71
+ - **New Types** - `UpscaleOptions`, `UpscaleResult`, `WsUpscaleRequest`, `WsUpscaleResponse`
72
+ - **New Constants** - `UPSCALE_FACTOR` (MIN: 2, MAX: 4, DEFAULT: 2)
73
+
74
+ ### v1.3.0
75
+
76
+ - **Full WebSocket Client** - Complete WebSocket implementation
77
+ - `wsConnect()`, `wsGenerateImage()`, `wsGenerateVideo()`, `wsGenerateLlm()`
78
+ - Auto-reconnect with exponential backoff
79
+ - Ping/pong keepalive to prevent timeouts
80
+ - Request tracking with `requestId`
81
+ - **Image Resize for img2img** - New `width` and `height` parameters for `editImage()`
82
+ - Resize output to custom dimensions (320-1344) during image editing
83
+ - **JSON Output Enhancement** - `TextGenerationResult.json` field for parsed JSON output
84
+ - **WebSocket Types** - New exported types for WebSocket requests/responses
85
+
86
+ ### v1.2.0
87
+
88
+ - **Video Frame Extraction** - Extract frames from videos with `?seek=<ms>` CDN parameter
89
+ - **Video Metadata** - `duration` and `fps` in video generation responses
90
+ - **WatermarkPosition Constants** - Type-safe watermark positioning
91
+
92
+ ### v1.1.0
93
+
94
+ - **Style Update** - Replaced `legacy` style with new `text` style
95
+ - Updated style presets to match Gen6 improvements
96
+
97
+ ### v1.0.3
98
+
99
+ - **Markdown Formatting** - New `useMarkdown` option for LLM text generation
100
+ - When enabled, responses include markdown formatting (headers, bullet points, code blocks)
101
+ - Works with both REST API and WebSocket connections
102
+
103
+ ### v1.0.2
104
+
105
+ - **JSON Format Fix** - Fixed `jsonFormat: true` responses returning empty strings
106
+ - LLM text generation now correctly returns JSON responses when using `jsonFormat` and `jsonTemplate` options
107
+
108
+ ### v1.0.1
109
+
110
+ - **GIF File Format Support** - Full GIF upload, download, and processing via CDN
111
+ - **Format Conversion** - Convert between image formats (PNG, JPG, GIF) and extract video frames as GIF
112
+ - **GIF Watermarking** - Apply watermarks to GIF files with position control
113
+ - **Enhanced Debug Logging** - Improved watermark operation debugging
114
+
115
+ ### v1.0.0
116
+
117
+ - Initial public release
118
+ - Text-to-image generation with 12 style presets
119
+ - Image-to-image editing with strength control
120
+ - Image-to-video generation
121
+ - LLM text generation with memory and JSON format support
122
+ - 7 format presets for different aspect ratios
123
+ - WebSocket and REST API support
124
+ - Built-in rate limiting and watermarking support
125
+
126
+ ---
127
+
128
+ ## Installation
129
+
130
+ ```bash
131
+ npm install 2dai-cloud-sdk
132
+ ```
133
+
134
+ or
135
+
136
+ ```bash
137
+ yarn add 2dai-cloud-sdk
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Getting an API Key
143
+
144
+ To use this SDK, you need an API key.
145
+
146
+ **Request Access:**
147
+ 1. Fill out the [API Access Request Form](https://forms.gle/TdQ8Wu3zpegvAwVg9)
148
+ 2. We'll review your request within 24-48 hours
149
+ 3. Once approved, you'll receive your API key via email
150
+
151
+ **Contact:**
152
+ - Twitter/X: [@2DAICommunity](https://x.com/2DAICommunity)
153
+ - Telegram: [Token2dAI](https://t.me/Token2dAI)
154
+
155
+ ---
156
+
157
+ ## Quick Start
158
+
159
+ ```typescript
160
+ import { createClient, STYLES, FORMATS } from '2dai-cloud-sdk';
161
+
162
+ // Initialize the client
163
+ const client = createClient('2dai_pk_your_api_key_here');
164
+
165
+ // Generate an image (REST API)
166
+ const image = await client.generateImage({
167
+ prompt: 'a majestic lion in the savanna at sunset',
168
+ style: STYLES.cine.id,
169
+ format: FORMATS.landscape.id
170
+ });
171
+
172
+ // Construct CDN URL from image ID
173
+ const imageUrl = `https://apiv2.2dai.io:800/api/v1/cdn/${image.imageId}.jpg`;
174
+ console.log(`Image URL: ${imageUrl}`);
175
+
176
+ // Or use WebSocket for real-time generation
177
+ await client.wsConnect();
178
+ const wsImage = await client.wsGenerateImage({
179
+ prompt: 'a futuristic city at night',
180
+ style: STYLES.cine.id,
181
+ format: FORMATS.landscape.id
182
+ });
183
+ console.log(`WebSocket Image ID: ${wsImage.result.imageId}`);
184
+ await client.close();
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Documentation
190
+
191
+ ### Initialization
192
+
193
+ ```typescript
194
+ import { createClient } from '2dai-cloud-sdk';
195
+
196
+ const client = createClient('2dai_pk_your_api_key_here', {
197
+ baseUrl: 'https://apiv2.2dai.io:800', // Optional, defaults to production
198
+ timeout: 300000, // Optional, 5 minutes default
199
+ debug: false // Optional, enable debug logging
200
+ });
201
+ ```
202
+
203
+ ### Image Generation
204
+
205
+ #### Generate from Text Prompt
206
+
207
+ ```typescript
208
+ import { STYLES, FORMATS } from '2dai-cloud-sdk';
209
+
210
+ const result = await client.generateImage({
211
+ prompt: 'a cyberpunk cityscape at night with neon lights',
212
+ negativePrompt: 'blurry, low quality, distorted',
213
+ style: STYLES.cine.id,
214
+ format: FORMATS.landscape.id,
215
+ seed: 12345 // Optional, for reproducibility
216
+ });
217
+
218
+ console.log(result);
219
+ // {
220
+ // success: true,
221
+ // imageId: '550e8400-e29b-41d4-a716-446655440000',
222
+ // width: 1344,
223
+ // height: 768,
224
+ // seed: 12345
225
+ // }
226
+
227
+ // Construct CDN URL from imageId
228
+ const imageUrl = `https://apiv2.2dai.io:800/api/v1/cdn/${result.imageId}.jpg`;
229
+ console.log(`Access image at: ${imageUrl}`);
230
+ ```
231
+
232
+ #### Available Styles
233
+
234
+ ```typescript
235
+ import { STYLES } from '2dai-cloud-sdk';
236
+
237
+ STYLES.raw // Raw Gen6 defaults
238
+ STYLES.realistic // Realistic 2DAI generation
239
+ STYLES.text // Text & Clarity
240
+ STYLES.ciniji // Niji anime style with vibrant colors
241
+ STYLES.portrait // Gen6 Portrait
242
+ STYLES.cine // Gen6 Cinematic
243
+ STYLES.sport // Gen6 Sport
244
+ STYLES.fashion // Gen6 Fashion
245
+ STYLES.niji // Gen6 Anime Niji
246
+ STYLES.anime // Gen6 Anime
247
+ STYLES.manga // Gen6 Manga
248
+ STYLES.paint // Gen6 Paint
249
+ ```
250
+
251
+ #### Available Formats
252
+
253
+ ```typescript
254
+ import { FORMATS } from '2dai-cloud-sdk';
255
+
256
+ FORMATS.portrait // 9:16 vertical (768x1344)
257
+ FORMATS.landscape // 16:9 horizontal (1344x768)
258
+ FORMATS.profile // 1:1 profile picture (1024x1024)
259
+ FORMATS.story // 9:16 story format (720x1280)
260
+ FORMATS.post // 9:7 wide square (1152x896)
261
+ FORMATS.smartphone // Phone screen (640x1344)
262
+ FORMATS.banner // 3:1 wide screen (1472x448)
263
+ ```
264
+
265
+ ### Image Editing
266
+
267
+ ```typescript
268
+ // Edit using image ID
269
+ const edited = await client.editImage('550e8400-e29b-41d4-a716-446655440000', {
270
+ prompt: 'make the sky more dramatic with storm clouds'
271
+ });
272
+
273
+ // Edit with resize - output to custom dimensions
274
+ const resized = await client.editImage('550e8400-e29b-41d4-a716-446655440000', {
275
+ prompt: 'Seamlessly extend the image, remove the black background',
276
+ width: 768, // Custom output width (320-1344)
277
+ height: 1344 // Custom output height (320-1344)
278
+ });
279
+
280
+ console.log(resized);
281
+ // {
282
+ // success: true,
283
+ // imageId: 'new-image-id',
284
+ // width: 768,
285
+ // height: 1344,
286
+ // seed: 12345
287
+ // }
288
+
289
+ // Edit with resizePad - fit original in frame with black padding
290
+ const padded = await client.editImage('550e8400-e29b-41d4-a716-446655440000', {
291
+ prompt: 'Seamlessly extend the image',
292
+ width: 768,
293
+ height: 1344,
294
+ resizePad: true // Fits original image in resized frame, fills extra space with black
295
+ });
296
+
297
+ // Edit via WebSocket
298
+ await client.wsConnect();
299
+ const wsEdited = await client.wsGenerateImage({
300
+ imageId: '550e8400-e29b-41d4-a716-446655440000',
301
+ prompt: 'make the image black and white',
302
+ width: 1344,
303
+ height: 768
304
+ });
305
+ console.log(wsEdited.result.imageId);
306
+ ```
307
+
308
+ #### Resize Padding (`resizePad`)
309
+
310
+ When resizing images to different dimensions, the `resizePad` option controls how the original image is fitted:
311
+
312
+ | resizePad | Behavior |
313
+ |-----------|----------|
314
+ | `false` (default) | Image is cropped/stretched to fill the target dimensions |
315
+ | `true` | Original image fits entirely within the frame, extra space is filled with black |
316
+
317
+ ### Image Upscaling
318
+
319
+ AI-powered image upscaling. No user prompt needed.
320
+
321
+ ```typescript
322
+ import { UPSCALE_FACTOR } from '2dai-cloud-sdk';
323
+
324
+ // Upscale an image (REST API)
325
+ const upscaled = await client.upscaleImage('550e8400-e29b-41d4-a716-446655440000', {
326
+ factor: 2 // 2x, 3x, or 4x (default: 2)
327
+ });
328
+
329
+ console.log(upscaled);
330
+ // {
331
+ // success: true,
332
+ // imageId: 'upscaled-image-id',
333
+ // width: 2048,
334
+ // height: 2048,
335
+ // seed: 12345
336
+ // }
337
+
338
+ // Upscale via WebSocket
339
+ await client.wsConnect();
340
+ const wsUpscaled = await client.wsUpscaleImage({
341
+ imageId: '550e8400-e29b-41d4-a716-446655440000',
342
+ factor: 2
343
+ });
344
+ console.log(wsUpscaled.result.imageId);
345
+ await client.close();
346
+ ```
347
+
348
+ #### Upscale Factor Limits
349
+
350
+ ```typescript
351
+ import { UPSCALE_FACTOR } from '2dai-cloud-sdk';
352
+
353
+ UPSCALE_FACTOR.MIN // 2
354
+ UPSCALE_FACTOR.MAX // 4
355
+ UPSCALE_FACTOR.DEFAULT // 2
356
+ ```
357
+
358
+ ### Video Generation
359
+
360
+ ```typescript
361
+ // Generate video from image ID
362
+ const video = await client.generateVideo({
363
+ imageId: '550e8400-e29b-41d4-a716-446655440000',
364
+ duration: 5, // 1-10 seconds
365
+ fps: 16 // 8-32 fps
366
+ });
367
+
368
+ // Generate video from buffer
369
+ const video = await client.generateVideo({
370
+ imageBuffer,
371
+ duration: 5
372
+ });
373
+
374
+ console.log(video);
375
+ // {
376
+ // success: true,
377
+ // videoId: '660e8400-e29b-41d4-a716-446655440000',
378
+ // duration: 5,
379
+ // fps: 16
380
+ // }
381
+
382
+ // Construct CDN URL from videoId
383
+ const videoUrl = `https://apiv2.2dai.io:800/api/v1/cdn/${video.videoId}.mp4`;
384
+ console.log(`Access video at: ${videoUrl}`);
385
+ ```
386
+
387
+ ### Text Generation (LLM)
388
+
389
+ ```typescript
390
+ // Simple text generation
391
+ const result = await client.generateText({
392
+ prompt: 'Write a short poem about AI and creativity'
393
+ });
394
+
395
+ console.log(result.response);
396
+
397
+ // With markdown formatting
398
+ const formatted = await client.generateText({
399
+ prompt: 'Explain what TypeScript is. Use headers and code examples.',
400
+ useMarkdown: true // Response includes markdown formatting
401
+ });
402
+
403
+ console.log(formatted.response); // Contains # headers, ```code blocks```, bullet points, etc.
404
+
405
+ // With system message and memory
406
+ const result = await client.generateText({
407
+ prompt: 'What were we discussing?',
408
+ system: 'You are a helpful assistant',
409
+ memory: [
410
+ 'User asked about AI capabilities',
411
+ 'Discussed image generation features'
412
+ ]
413
+ });
414
+
415
+ // JSON format response
416
+ const result = await client.generateText({
417
+ prompt: 'Analyze this product review',
418
+ jsonFormat: true,
419
+ jsonTemplate: {
420
+ sentiment: 'positive/negative/neutral',
421
+ score: '1-10',
422
+ summary: 'brief summary'
423
+ }
424
+ });
425
+
426
+ console.log(result.response); // Returns structured JSON
427
+
428
+ // With knowledge base
429
+ const result = await client.generateText({
430
+ prompt: 'What is our refund policy?',
431
+ askKnowledge: {
432
+ sources: ['docs', 'policies'],
433
+ query: 'refund policy'
434
+ }
435
+ });
436
+
437
+ // Image description (Vision)
438
+ const imageResult = await client.generateImage({
439
+ prompt: 'a futuristic city at night'
440
+ });
441
+
442
+ const description = await client.generateText({
443
+ prompt: 'Analyze this image and extract structured data',
444
+ system: 'Extract information from images into structured JSON format.',
445
+ imageId: imageResult.imageId, // Pass image for analysis
446
+ jsonFormat: true,
447
+ jsonTemplate: {
448
+ main_subject: 'string - primary subject of the image',
449
+ objects: 'array of strings - objects visible in the image',
450
+ colors: 'array of strings - dominant colors',
451
+ mood: 'string - overall mood/atmosphere'
452
+ }
453
+ });
454
+
455
+ console.log(description.json);
456
+ // Output: { main_subject: "futuristic cityscape", objects: ["buildings", "lights", ...], ... }
457
+ ```
458
+
459
+ ### CDN Operations
460
+
461
+ The CDN supports multiple file formats and operations including format conversion, resizing, and watermarking.
462
+
463
+ #### Supported File Formats
464
+
465
+ | Format | Extension | Description |
466
+ |--------|-----------|-------------|
467
+ | JPEG | `.jpg`, `.jpeg` | Standard image format |
468
+ | PNG | `.png` | Lossless image format with transparency |
469
+ | GIF | `.gif` | Static or animated image format |
470
+ | MP4 | `.mp4` | Video format |
471
+
472
+ #### CDN URL Construction
473
+
474
+ ```typescript
475
+ // Basic image URL
476
+ const imageUrl = `https://apiv2.2dai.io:800/api/v1/cdn/${imageId}.jpg`;
477
+
478
+ // Image with resize
479
+ const resizedUrl = `https://apiv2.2dai.io:800/api/v1/cdn/${imageId}.jpg?w=512&h=512`;
480
+
481
+ // Image with watermark
482
+ const watermarkedUrl = `https://apiv2.2dai.io:800/api/v1/cdn/${imageId}.jpg?watermark=${watermarkId}&position=southeast`;
483
+
484
+ // Video URL
485
+ const videoUrl = `https://apiv2.2dai.io:800/api/v1/cdn/${videoId}.mp4`;
486
+
487
+ // Extract first frame from video as GIF
488
+ const gifFromVideo = `https://apiv2.2dai.io:800/api/v1/cdn/${videoId}.gif`;
489
+
490
+ // GIF with resize
491
+ const resizedGif = `https://apiv2.2dai.io:800/api/v1/cdn/${videoId}.gif?w=256&h=256`;
492
+ ```
493
+
494
+ #### Format Conversion Matrix
495
+
496
+ | From \ To | PNG | JPG | JPEG | GIF |
497
+ |-----------|-----|-----|------|-----|
498
+ | PNG | - | Yes | Yes | Yes |
499
+ | JPG | Yes | - | Yes | Yes |
500
+ | JPEG | Yes | Yes | - | Yes |
501
+ | GIF | Yes | Yes | Yes | - |
502
+ | MP4 | Yes | Yes | Yes | Yes |
503
+
504
+ **Notes:**
505
+ - Converting animated GIF to other formats extracts the first frame
506
+ - MP4 to image formats extracts a single frame as still image
507
+ - Watermarking supported on all image formats including GIF
508
+
509
+ #### CDN Query Parameters
510
+
511
+ | Parameter | Description | Example |
512
+ |-----------|-------------|---------|
513
+ | `w` | Target width in pixels | `?w=1024` |
514
+ | `h` | Target height in pixels | `?h=768` |
515
+ | `watermark` | Watermark file ID | `?watermark=abc123` |
516
+ | `position` | Watermark position | `?position=southeast` |
517
+
518
+ **Watermark Positions:**
519
+ - Sharp gravity: `northwest`, `north`, `northeast`, `west`, `center`, `east`, `southwest`, `south`, `southeast`
520
+ - Human-readable: `top-left`, `top-center`, `top-right`, `middle-left`, `middle-center`, `middle-right`, `bottom-left`, `bottom-center`, `bottom-right`
521
+
522
+ #### Downloading Files
523
+
524
+ ```typescript
525
+ import axios from 'axios';
526
+ import fs from 'fs';
527
+
528
+ // Download image
529
+ const imageResponse = await axios.get(
530
+ `https://apiv2.2dai.io:800/api/v1/cdn/${imageId}.jpg`,
531
+ {
532
+ responseType: 'arraybuffer',
533
+ headers: { 'Authorization': `Bearer ${apiKey}` }
534
+ }
535
+ );
536
+ fs.writeFileSync('output.jpg', imageResponse.data);
537
+
538
+ // Download video
539
+ const videoResponse = await axios.get(
540
+ `https://apiv2.2dai.io:800/api/v1/cdn/${videoId}.mp4`,
541
+ {
542
+ responseType: 'arraybuffer',
543
+ headers: { 'Authorization': `Bearer ${apiKey}` }
544
+ }
545
+ );
546
+ fs.writeFileSync('output.mp4', videoResponse.data);
547
+
548
+ // Extract video frame as GIF
549
+ const gifResponse = await axios.get(
550
+ `https://apiv2.2dai.io:800/api/v1/cdn/${videoId}.gif`,
551
+ {
552
+ responseType: 'arraybuffer',
553
+ headers: { 'Authorization': `Bearer ${apiKey}` }
554
+ }
555
+ );
556
+ fs.writeFileSync('frame.gif', gifResponse.data);
557
+
558
+ // Download with resize
559
+ const resizedResponse = await axios.get(
560
+ `https://apiv2.2dai.io:800/api/v1/cdn/${imageId}.png?w=256&h=256`,
561
+ {
562
+ responseType: 'arraybuffer',
563
+ headers: { 'Authorization': `Bearer ${apiKey}` }
564
+ }
565
+ );
566
+ fs.writeFileSync('thumbnail.png', resizedResponse.data);
567
+ ```
568
+
569
+ ### Watermarking
570
+
571
+ ```typescript
572
+ // Upload watermark and set as default
573
+ const settings = await client.getSettings();
574
+ await client.updateWatermark('watermark-cdn-id');
575
+
576
+ // Apply watermark to generation
577
+ const image = await client.generateImage({
578
+ prompt: 'a beautiful landscape',
579
+ watermark: 'watermark-cdn-id',
580
+ watermarkPosition: 'southeast', // bottom-right
581
+ copyright: '2024 My Company'
582
+ });
583
+
584
+ // Tiled watermark (images only)
585
+ const image = await client.generateImage({
586
+ prompt: 'product photography',
587
+ watermark: 'watermark-cdn-id',
588
+ watermarkAsTiles: true
589
+ });
590
+
591
+ // Apply watermark via CDN URL
592
+ const watermarkedUrl = `https://apiv2.2dai.io:800/api/v1/cdn/${imageId}.jpg?watermark=${watermarkId}&position=center`;
593
+ ```
594
+
595
+ ### Settings & Usage Tracking
596
+
597
+ ```typescript
598
+ // Get API key settings with current usage
599
+ const settings = await client.getSettings();
600
+
601
+ console.log('API Key:', settings.name);
602
+ console.log('Status:', settings.status);
603
+ console.log('\nRate Limits:');
604
+ console.log('Image:', settings.rateLimits.image);
605
+ console.log('Video:', settings.rateLimits.video);
606
+ console.log('LLM:', settings.rateLimits.llm);
607
+ console.log('CDN:', settings.rateLimits.cdn);
608
+
609
+ // Current usage with remaining quota and reset times
610
+ console.log('\nCurrent Usage:');
611
+ console.log('Image:', {
612
+ used15min: settings.currentUsage.image.current.requestsPer15Min,
613
+ usedDaily: settings.currentUsage.image.current.requestsPerDay,
614
+ remaining15min: settings.currentUsage.image.remaining.requestsPer15Min,
615
+ remainingDaily: settings.currentUsage.image.remaining.requestsPerDay,
616
+ resetAt: settings.currentUsage.image.resetAt
617
+ });
618
+
619
+ // LLM usage includes token tracking
620
+ console.log('LLM:', {
621
+ requests15min: settings.currentUsage.llm.current.requestsPer15Min,
622
+ requestsDaily: settings.currentUsage.llm.current.requestsPerDay,
623
+ tokens15min: settings.currentUsage.llm.current.tokensPer15Min,
624
+ tokensDaily: settings.currentUsage.llm.current.tokensPerDay,
625
+ resetAt: settings.currentUsage.llm.resetAt
626
+ });
627
+ ```
628
+
629
+ ### Rate Limit Checking
630
+
631
+ ```typescript
632
+ // Check current rate limits for all operations
633
+ const limits = await client.checkLimits();
634
+
635
+ limits.forEach(limit => {
636
+ console.log(`${limit.operation}:`);
637
+ console.log(` Requests: ${limit.current.requestsPer15Min}/${limit.limit.requestsPer15Min} (15min)`);
638
+ console.log(` Requests: ${limit.current.requestsPerDay}/${limit.limit.requestsPerDay} (daily)`);
639
+
640
+ if (limit.operation === 'llm') {
641
+ console.log(` Tokens: ${limit.current.tokensPer15Min}/${limit.limit.tokensPer15Min} (15min)`);
642
+ }
643
+ });
644
+ ```
645
+
646
+ ### Health Check
647
+
648
+ ```typescript
649
+ const health = await client.health();
650
+ console.log(health);
651
+ // {
652
+ // status: 'ok',
653
+ // timestamp: '2024-01-15T10:30:00.000Z'
654
+ // }
655
+ ```
656
+
657
+ ---
658
+
659
+ ## Advanced Usage
660
+
661
+ ### Custom Dimensions
662
+
663
+ ```typescript
664
+ const image = await client.generateImage({
665
+ prompt: 'a mountain landscape',
666
+ width: 1280,
667
+ height: 720
668
+ // Note: width/height must be between 320-1344
669
+ });
670
+ ```
671
+
672
+ ### Debug Mode
673
+
674
+ ```typescript
675
+ const client = createClient('2dai_pk_your_api_key_here', {
676
+ debug: true // Enable detailed logging
677
+ });
678
+ ```
679
+
680
+ ---
681
+
682
+ ## Complete Examples
683
+
684
+ ### Example 1: Generate Image with Watermark and Download
685
+
686
+ ```typescript
687
+ import { createClient, STYLES, FORMATS } from '2dai-cloud-sdk';
688
+ import axios from 'axios';
689
+ import fs from 'fs';
690
+
691
+ const client = createClient('2dai_pk_your_api_key_here');
692
+
693
+ async function generateAndDownload() {
694
+ try {
695
+ // Generate image
696
+ const result = await client.generateImage({
697
+ prompt: 'a futuristic city at sunset with flying cars',
698
+ style: STYLES.cine.id,
699
+ format: FORMATS.landscape.id,
700
+ watermark: 'your-watermark-id',
701
+ watermarkPosition: 'southeast'
702
+ });
703
+
704
+ console.log('Image generated:', result.imageId);
705
+
706
+ // Download the image
707
+ const imageUrl = `https://apiv2.2dai.io:800/api/v1/cdn/${result.imageId}.jpg`;
708
+ const response = await axios.get(imageUrl, {
709
+ responseType: 'arraybuffer',
710
+ headers: { 'Authorization': `Bearer 2dai_pk_your_api_key_here` }
711
+ });
712
+
713
+ fs.writeFileSync('generated-image.jpg', response.data);
714
+ console.log('Image saved to generated-image.jpg');
715
+
716
+ } catch (error) {
717
+ console.error('Error:', error.message);
718
+ }
719
+ }
720
+
721
+ generateAndDownload();
722
+ ```
723
+
724
+ ### Example 2: Generate Video and Extract GIF Frame
725
+
726
+ ```typescript
727
+ import { createClient, STYLES, FORMATS } from '2dai-cloud-sdk';
728
+ import axios from 'axios';
729
+ import fs from 'fs';
730
+
731
+ const client = createClient('2dai_pk_your_api_key_here');
732
+
733
+ async function generateVideoAndExtractFrame() {
734
+ try {
735
+ // First generate an image
736
+ const image = await client.generateImage({
737
+ prompt: 'a dancing robot in a disco',
738
+ style: STYLES.anime.id,
739
+ format: FORMATS.profile.id
740
+ });
741
+ console.log('Image generated:', image.imageId);
742
+
743
+ // Generate video from image
744
+ const video = await client.generateVideo({
745
+ imageId: image.imageId,
746
+ duration: 3,
747
+ fps: 16
748
+ });
749
+ console.log('Video generated:', video.videoId);
750
+
751
+ // Download video as MP4
752
+ const mp4Url = `https://apiv2.2dai.io:800/api/v1/cdn/${video.videoId}.mp4`;
753
+ const mp4Response = await axios.get(mp4Url, {
754
+ responseType: 'arraybuffer',
755
+ headers: { 'Authorization': `Bearer 2dai_pk_your_api_key_here` }
756
+ });
757
+ fs.writeFileSync('video.mp4', mp4Response.data);
758
+ console.log('Video saved to video.mp4');
759
+
760
+ // Extract first frame as GIF
761
+ const gifUrl = `https://apiv2.2dai.io:800/api/v1/cdn/${video.videoId}.gif`;
762
+ const gifResponse = await axios.get(gifUrl, {
763
+ responseType: 'arraybuffer',
764
+ headers: { 'Authorization': `Bearer 2dai_pk_your_api_key_here` }
765
+ });
766
+ fs.writeFileSync('thumbnail.gif', gifResponse.data);
767
+ console.log('GIF thumbnail saved to thumbnail.gif');
768
+
769
+ } catch (error) {
770
+ console.error('Error:', error.message);
771
+ }
772
+ }
773
+
774
+ generateVideoAndExtractFrame();
775
+ ```
776
+
777
+ ### Example 3: LLM with Memory and JSON Output
778
+
779
+ ```typescript
780
+ import { createClient } from '2dai-cloud-sdk';
781
+
782
+ const client = createClient('2dai_pk_your_api_key_here');
783
+
784
+ async function chatWithMemory() {
785
+ const memory: string[] = [];
786
+
787
+ // First message
788
+ const response1 = await client.generateText({
789
+ prompt: 'My name is Alice and I love painting',
790
+ system: 'You are a friendly art assistant'
791
+ });
792
+ console.log('Response 1:', response1.response);
793
+ memory.push('User: My name is Alice and I love painting');
794
+ memory.push(`Assistant: ${response1.response}`);
795
+
796
+ // Second message with memory
797
+ const response2 = await client.generateText({
798
+ prompt: 'What art supplies would you recommend for me?',
799
+ system: 'You are a friendly art assistant',
800
+ memory
801
+ });
802
+ console.log('Response 2:', response2.response);
803
+
804
+ // Get structured JSON output
805
+ const analysis = await client.generateText({
806
+ prompt: 'Analyze the conversation we just had',
807
+ system: 'Analyze conversations and return structured data',
808
+ memory,
809
+ jsonFormat: true,
810
+ jsonTemplate: {
811
+ userName: 'the user name mentioned',
812
+ interests: 'array of user interests',
813
+ recommendationsGiven: 'number of recommendations',
814
+ sentiment: 'positive/neutral/negative'
815
+ }
816
+ });
817
+ console.log('Analysis:', JSON.parse(analysis.response));
818
+ }
819
+
820
+ chatWithMemory();
821
+ ```
822
+
823
+ ### Example 4: Batch Image Generation with Rate Limit Checking
824
+
825
+ ```typescript
826
+ import { createClient, STYLES } from '2dai-cloud-sdk';
827
+
828
+ const client = createClient('2dai_pk_your_api_key_here');
829
+
830
+ async function batchGenerate(prompts: string[]) {
831
+ const results = [];
832
+
833
+ for (const prompt of prompts) {
834
+ // Check rate limits before each request
835
+ const limits = await client.checkLimits();
836
+ const imageLimit = limits.find(l => l.operation === 'image');
837
+
838
+ if (imageLimit && imageLimit.remaining.requestsPer15Min <= 0) {
839
+ console.log('Rate limit reached, waiting for reset...');
840
+ const resetTime = new Date(imageLimit.resetAt.window15Min);
841
+ const waitMs = resetTime.getTime() - Date.now();
842
+ await new Promise(resolve => setTimeout(resolve, waitMs + 1000));
843
+ }
844
+
845
+ try {
846
+ const result = await client.generateImage({
847
+ prompt,
848
+ style: STYLES.realistic.id
849
+ });
850
+ results.push({ prompt, imageId: result.imageId, success: true });
851
+ console.log(`Generated: ${prompt.substring(0, 30)}... -> ${result.imageId}`);
852
+ } catch (error: any) {
853
+ results.push({ prompt, error: error.message, success: false });
854
+ console.error(`Failed: ${prompt.substring(0, 30)}... -> ${error.message}`);
855
+ }
856
+ }
857
+
858
+ return results;
859
+ }
860
+
861
+ // Usage
862
+ const prompts = [
863
+ 'a red apple on a wooden table',
864
+ 'a blue ocean with waves',
865
+ 'a green forest with sunlight'
866
+ ];
867
+
868
+ batchGenerate(prompts).then(results => {
869
+ console.log('Batch complete:', results);
870
+ });
871
+ ```
872
+
873
+ ---
874
+
875
+ ## API Reference
876
+
877
+ ### Types
878
+
879
+ ```typescript
880
+ interface ImageGenerationOptions {
881
+ prompt: string;
882
+ style?: StylePreset | string;
883
+ format?: FormatPreset | string;
884
+ negativePrompt?: string;
885
+ seed?: number;
886
+ width?: number;
887
+ height?: number;
888
+ watermark?: string;
889
+ watermarkPosition?: WatermarkPosition;
890
+ watermarkAsTiles?: boolean;
891
+ copyright?: string;
892
+ }
893
+
894
+ interface VideoGenerationOptions {
895
+ imageId?: string;
896
+ imageBuffer?: Buffer;
897
+ duration?: number; // 1-10 seconds
898
+ fps?: number; // 8-32 fps
899
+ watermark?: string;
900
+ watermarkPosition?: WatermarkPosition;
901
+ }
902
+
903
+ interface TextGenerationOptions {
904
+ prompt: string;
905
+ system?: string;
906
+ memory?: string[];
907
+ jsonFormat?: boolean;
908
+ jsonTemplate?: { [key: string]: string };
909
+ useRandomSeed?: boolean;
910
+ askKnowledge?: {
911
+ sources?: string[];
912
+ query?: string;
913
+ };
914
+ useMarkdown?: boolean; // Enable markdown formatting in response (default: false)
915
+ imageId?: string; // Image ID from CDN for vision/image analysis
916
+ }
917
+
918
+ interface UpscaleOptions {
919
+ factor?: number; // 2, 3, or 4 (default: 2)
920
+ seed?: number; // Optional seed for reproducibility
921
+ }
922
+
923
+ interface UpscaleResult {
924
+ success: boolean;
925
+ imageId: string;
926
+ width: number;
927
+ height: number;
928
+ seed?: number;
929
+ }
930
+
931
+ interface OperationUsage {
932
+ current: {
933
+ requestsPer15Min: number;
934
+ requestsPerDay: number;
935
+ tokensPer15Min?: number; // Only for LLM operations
936
+ tokensPerDay?: number; // Only for LLM operations
937
+ };
938
+ remaining: {
939
+ requestsPer15Min: number;
940
+ requestsPerDay: number;
941
+ };
942
+ resetAt: {
943
+ window15Min: string; // ISO timestamp
944
+ daily: string; // ISO timestamp
945
+ };
946
+ }
947
+
948
+ interface APIKeySettings {
949
+ key: string;
950
+ name: string;
951
+ status: 'active' | 'suspended';
952
+ rateLimits: {
953
+ image: RateLimitConfig;
954
+ video: RateLimitConfig;
955
+ llm: LLMRateLimitConfig;
956
+ cdn: RateLimitConfig;
957
+ };
958
+ currentUsage?: {
959
+ image: OperationUsage;
960
+ video: OperationUsage;
961
+ llm: OperationUsage;
962
+ cdn: OperationUsage;
963
+ };
964
+ llmSettings?: any;
965
+ createdAt?: string;
966
+ lastUsedAt?: string;
967
+ }
968
+
969
+ type WatermarkPosition =
970
+ // Sharp gravity constants (recommended)
971
+ | 'center'
972
+ | 'northwest' | 'north' | 'northeast'
973
+ | 'west' | 'east'
974
+ | 'southwest' | 'south' | 'southeast'
975
+ // Human-readable alternatives
976
+ | 'top-left' | 'top-center' | 'top-right'
977
+ | 'middle-left' | 'middle-center' | 'middle-right'
978
+ | 'bottom-left' | 'bottom-center' | 'bottom-right';
979
+ ```
980
+
981
+ ---
982
+
983
+ ## Rate Limits
984
+
985
+ Default rate limits per API key:
986
+
987
+ | Operation | 15-Min Window | Daily Limit |
988
+ |-----------|--------------|-------------|
989
+ | Image Generation | 150 requests | 1,000 requests |
990
+ | Video Generation | 30 requests | 200 requests |
991
+ | LLM | 300 requests | 1,000 requests |
992
+ | LLM Tokens | 1,500,000 tokens | 5,000,000 tokens |
993
+ | CDN | 1,500 requests | 10,000 requests |
994
+
995
+ Custom rate limits can be configured per API key.
996
+
997
+ ---
998
+
999
+ ## Best Practices
1000
+
1001
+ ### 1. Check Rate Limits Before Batch Operations
1002
+
1003
+ ```typescript
1004
+ const limits = await client.checkLimits();
1005
+ const remaining = limits.find(l => l.operation === 'image')?.remaining.requestsPer15Min;
1006
+
1007
+ if (remaining && remaining < batchSize) {
1008
+ console.log(`Only ${remaining} requests available, reducing batch size`);
1009
+ }
1010
+ ```
1011
+
1012
+ ### 2. Use Appropriate Styles for Your Use Case
1013
+
1014
+ ```typescript
1015
+ import { STYLES } from '2dai-cloud-sdk';
1016
+
1017
+ // For realistic photos
1018
+ STYLES.realistic.id
1019
+
1020
+ // For artistic/creative content
1021
+ STYLES.paint.id
1022
+
1023
+ // For anime/manga content
1024
+ STYLES.anime.id
1025
+ STYLES.manga.id
1026
+
1027
+ // For cinematic shots
1028
+ STYLES.cine.id
1029
+ ```
1030
+
1031
+ ### 3. Handle Errors Gracefully
1032
+
1033
+ ```typescript
1034
+ try {
1035
+ const result = await client.generateImage({ prompt: 'test' });
1036
+ } catch (error: any) {
1037
+ if (error.message.includes('RATE_LIMIT_EXCEEDED')) {
1038
+ // Wait and retry
1039
+ await new Promise(r => setTimeout(r, 60000));
1040
+ } else if (error.message.includes('INVALID_API_KEY')) {
1041
+ // Check API key configuration
1042
+ } else {
1043
+ // Log and handle other errors
1044
+ console.error('Generation failed:', error.message);
1045
+ }
1046
+ }
1047
+ ```
1048
+
1049
+ ### 4. Use Negative Prompts for Better Results (not fully supported yet)
1050
+
1051
+ ```typescript
1052
+ const result = await client.generateImage({
1053
+ prompt: 'a beautiful portrait of a woman',
1054
+ negativePrompt: 'blurry, distorted, low quality, bad anatomy, extra limbs'
1055
+ });
1056
+ ```
1057
+
1058
+ ### 5. Cache Generated Content
1059
+
1060
+ ```typescript
1061
+ // Store image IDs for reuse
1062
+ const cache = new Map<string, string>();
1063
+
1064
+ async function getOrGenerate(prompt: string): Promise<string> {
1065
+ if (cache.has(prompt)) {
1066
+ return cache.get(prompt)!;
1067
+ }
1068
+
1069
+ const result = await client.generateImage({ prompt });
1070
+ cache.set(prompt, result.imageId);
1071
+ return result.imageId;
1072
+ }
1073
+ ```
1074
+
1075
+ ### 6. Use Seeds for Reproducibility
1076
+
1077
+ ```typescript
1078
+ // Same seed = same result (with same prompt and settings)
1079
+ const result1 = await client.generateImage({
1080
+ prompt: 'a red car',
1081
+ seed: 12345
1082
+ });
1083
+
1084
+ const result2 = await client.generateImage({
1085
+ prompt: 'a red car',
1086
+ seed: 12345
1087
+ });
1088
+
1089
+ // result1.imageId content will be identical to result2.imageId
1090
+ ```
1091
+
1092
+ ---
1093
+
1094
+ ## Troubleshooting
1095
+
1096
+ ### Common Issues
1097
+
1098
+ #### "RATE_LIMIT_EXCEEDED" Error
1099
+
1100
+ **Problem:** You've exceeded your rate limit for the current time window.
1101
+
1102
+ **Solution:**
1103
+ ```typescript
1104
+ const settings = await client.getSettings();
1105
+ const resetTime = settings.currentUsage?.image.resetAt.window15Min;
1106
+ console.log(`Rate limit resets at: ${resetTime}`);
1107
+
1108
+ // Wait for reset
1109
+ const waitMs = new Date(resetTime).getTime() - Date.now();
1110
+ await new Promise(r => setTimeout(r, waitMs + 1000));
1111
+ ```
1112
+
1113
+ #### "INVALID_API_KEY" Error
1114
+
1115
+ **Problem:** Your API key is invalid or expired.
1116
+
1117
+ **Solution:**
1118
+ - Verify your API key starts with `2dai_pk_`
1119
+ - Check that the key hasn't been revoked
1120
+ - Ensure you're using the correct environment (production vs staging)
1121
+
1122
+ #### Timeout Errors
1123
+
1124
+ **Problem:** Requests are timing out.
1125
+
1126
+ **Solution:**
1127
+ ```typescript
1128
+ const client = createClient('2dai_pk_...', {
1129
+ timeout: 600000 // Increase to 10 minutes for long operations
1130
+ });
1131
+ ```
1132
+
1133
+ #### Image Quality Issues
1134
+
1135
+ **Problem:** Generated images don't match expectations.
1136
+
1137
+ **Solution:**
1138
+ - Use more detailed, descriptive prompts (include lighting, style, mood, composition)
1139
+ - Add negative prompts to exclude unwanted elements (requires style support)
1140
+ - Try different styles for your use case (see [Available Styles](#available-styles))
1141
+ - Use specific dimensions with the `format` parameter
1142
+ - Experiment with different seeds for variations
1143
+
1144
+ **Need custom models or advanced features?** Contact us at [support@2dai.io](mailto:support@2dai.io) for:
1145
+ - Custom fine-tuned models for your brand/style
1146
+ - Higher rate limits and enterprise plans
1147
+ - Priority support and dedicated infrastructure
1148
+
1149
+ #### CDN Download Failures
1150
+
1151
+ **Problem:** Cannot download files from CDN.
1152
+
1153
+ **Solution:**
1154
+ ```typescript
1155
+ // Ensure Authorization header is included
1156
+ const response = await axios.get(cdnUrl, {
1157
+ headers: { 'Authorization': `Bearer ${apiKey}` },
1158
+ responseType: 'arraybuffer',
1159
+ timeout: 30000
1160
+ });
1161
+ ```
1162
+
1163
+ ### Debug Mode
1164
+
1165
+ Enable debug mode to see detailed request/response logs:
1166
+
1167
+ ```typescript
1168
+ const client = createClient('2dai_pk_...', {
1169
+ debug: true
1170
+ });
1171
+ ```
1172
+
1173
+ ---
1174
+
1175
+ ## Migration Guide
1176
+
1177
+ ### Breaking Changes in v1.0.0
1178
+
1179
+ **`imageUrl` and `videoUrl` have been removed from generation responses.**
1180
+
1181
+ Previously, the API returned full CDN URLs directly in the response. For security and architectural reasons, these have been removed. You now receive only file IDs and must construct CDN URLs yourself.
1182
+
1183
+ #### Before (Old Version)
1184
+ ```typescript
1185
+ const result = await client.generateImage({ prompt: "..." });
1186
+ console.log(result.imageUrl); // Full URL was provided
1187
+ // "https://cdn.2dai.com/file/xyz.jpg?watermark=..."
1188
+ ```
1189
+
1190
+ #### After (Current Version)
1191
+ ```typescript
1192
+ const result = await client.generateImage({ prompt: "..." });
1193
+ // Result now contains only imageId (no imageUrl property)
1194
+
1195
+ // Construct the CDN URL from the imageId
1196
+ const imageUrl = `https://apiv2.2dai.io:800/api/v1/cdn/${result.imageId}.jpg`;
1197
+ console.log(imageUrl);
1198
+ ```
1199
+
1200
+ **Why this change?**
1201
+ - **Security**: Prevents exposure of internal CDN structure and watermark IDs
1202
+ - **Flexibility**: Allows you to customize CDN parameters (size, watermark position, etc.)
1203
+ - **Future-proof**: CDN infrastructure can be updated without breaking client code
1204
+
1205
+ ---
1206
+
1207
+ ## Testing
1208
+
1209
+ This SDK includes comprehensive test suites covering all API functionality:
1210
+
1211
+ ```bash
1212
+ # Run all tests (REST + WebSocket)
1213
+ npm test
1214
+
1215
+ # Run REST API tests only
1216
+ npm run test:rest
1217
+
1218
+ # Run WebSocket tests only
1219
+ npm run test:ws
1220
+
1221
+ # Run tests with coverage report
1222
+ npm run test:coverage
1223
+ ```
1224
+
1225
+ ### Test Performance
1226
+
1227
+ | Test Suite | Tests | Expected Time | Notes |
1228
+ |------------|-------|---------------|-------|
1229
+ | REST API (`test:rest`) | 20 | ~6-7 min | Image/video generation dominates |
1230
+ | WebSocket (`test:ws`) | 26 | ~6 min | Similar generation overhead |
1231
+ | Full Suite (`test`) | 46 | ~12-13 min | Both suites combined |
1232
+
1233
+ **Time breakdown by operation type:**
1234
+ - Image Generation: ~25-65s per test (AI processing)
1235
+ - Video Generation: ~90s per test (longest operation)
1236
+ - Image Upscale: ~70-80s per test
1237
+ - LLM Text Generation: ~1-10s per test (fast)
1238
+ - CDN Operations: <1s per test (instant)
1239
+ - Error Handling: <1s per test (validation only)
1240
+
1241
+ ### Test Coverage
1242
+
1243
+ **REST API Tests (20 tests):**
1244
+ - Image Generation (2): Default settings, style + format
1245
+ - Image Editing (2): Basic edit, resize with dimensions
1246
+ - Image Upscale (1): AI upscale 2x
1247
+ - Video + CDN (4): Generation, MP4-to-GIF, resize
1248
+ - LLM Text (5): Simple prompt, system + memory, JSON, vision, markdown
1249
+ - Error Handling (3): Invalid API key, params, IDs
1250
+ - Settings (1): API key settings with usage
1251
+ - Frame Extraction (2): Multiple formats, resize + watermark
1252
+ - Watermarks (1): Position constants
1253
+
1254
+ **WebSocket Tests (26 tests):**
1255
+ - Connection & Auth (4): Connect, authenticate, reject invalid, ping/pong
1256
+ - Image Generation (2): Default, style + format
1257
+ - Image Editing (2): Basic edit, resize
1258
+ - Image Upscale (1): AI upscale via WS
1259
+ - Video Generation (1): 5-second video
1260
+ - GIF/CDN (3): MP4-to-GIF, resize, position
1261
+ - LLM Generation (4): Simple, system + memory + JSON, vision, markdown
1262
+ - Error Handling (3): Missing prompt, invalid ID, invalid dimensions
1263
+ - Connection Resilience (2): Graceful close, reconnection
1264
+ - Client SDK Options (4): Connection state, WS options, timeout, REST integration
1265
+
1266
+ ### Test Output
1267
+
1268
+ All tests display timestamps for performance monitoring:
1269
+ ```
1270
+ ================================================================================
1271
+ 🧪 TEST: Generate Image - Default Settings
1272
+ ⏱️ [20:23:05] Total: 0.0s | Since last: 0.0s
1273
+ ================================================================================
1274
+ ```
1275
+
1276
+ Generated files are saved to `tests/tmp/` for manual verification.
1277
+
1278
+ ---
1279
+
1280
+ ## Requirements
1281
+
1282
+ - Node.js 18 or higher
1283
+ - TypeScript 5.0+ (for TypeScript projects)
1284
+
1285
+ ---
1286
+
1287
+ ## License
1288
+
1289
+ MIT