# Sogni SDK - Complete LLM Reference

> @sogni-ai/sogni-client - JavaScript/Node.js SDK for AI image, video, and audio generation
> Version: 4.x | License: ISC | Platform: Node.js & Browser
> Repository: https://github.com/Sogni-AI/sogni-client
> API Docs: https://sdk-docs.sogni.ai

================================================================================
TABLE OF CONTENTS
================================================================================

1. Overview & Installation
2. Quick Start Examples
3. Client Initialization
4. Image Generation
5. Video Generation - WAN 2.2 Models
6. Video Generation - LTX-2 Models
7. Audio Generation - ACE-Step 1.5 Models
8. LLM Text Generation
9. LLM Tool Calling & Sogni Platform Tools
10. Complete Parameter Reference
11. Events & Progress Tracking
12. Models, Presets & Options
13. Cost Estimation
14. Error Handling
15. Advanced Patterns
16. Type Definitions

================================================================================
1. OVERVIEW & INSTALLATION
================================================================================

The Sogni SDK provides access to the Sogni Supernet - a decentralized network
for AI inference. It supports:

- Image Generation: Stable Diffusion, Flux, Z-Image, Z-Image Turbo, Qwen Image Edit
- Video Generation: WAN 2.2 (5 workflows), LTX-2 models
- Audio Generation: ACE-Step 1.5 music generation (Turbo and SFT models)
- LLM Text Generation: Chat completions with streaming, multi-turn, and thinking mode
- LLM Tool Calling: OpenAI-compatible function calling with custom and platform tools
- Sogni Platform Tools: Generate images, videos, and music via natural language chat
- Real-time Progress: WebSocket-based event system
- Multiple Networks: Fast (GPU) and Relaxed (Mac)

INSTALLATION:
```bash
npm install @sogni-ai/sogni-client
```

REQUIREMENTS:
- Node.js 18+ or modern browser
- Sogni account with token balance (free signup at app.sogni.ai)

================================================================================
2. QUICK START EXAMPLES
================================================================================

MINIMAL IMAGE GENERATION:
```javascript
import { SogniClient } from '@sogni-ai/sogni-client';

// Option 1: API key auth (recommended) — no login() needed
const sogni = await SogniClient.createInstance({ appId: 'my-unique-app-id', apiKey: 'your-api-key' });

// Option 2: Username/password auth
// const sogni = await SogniClient.createInstance({ appId: 'my-unique-app-id' });
// await sogni.account.login('username', 'password');

await sogni.projects.waitForModels();

const project = await sogni.projects.create({
  type: 'image',
  modelId: 'flux1-schnell-fp8',
  positivePrompt: 'A cat wearing a hat',
  numberOfMedia: 1,
  steps: 4,
  guidance: 1
});

const urls = await project.waitForCompletion();
console.log('Image URL:', urls[0]); // Valid for 24 hours
```

MINIMAL VIDEO GENERATION:
```javascript
const project = await sogni.projects.create({
  type: 'video',
  network: 'fast',
  modelId: 'wan_v2.2-14b-fp8_t2v_lightx2v',
  positivePrompt: 'Ocean waves crashing on a beach at sunset',
  numberOfMedia: 1,
  duration: 5,
  fps: 16
});

const urls = await project.waitForCompletion();
console.log('Video URL:', urls[0]);
```

================================================================================
3. CLIENT INITIALIZATION
================================================================================

CREATING THE CLIENT:
```javascript
import { SogniClient } from '@sogni-ai/sogni-client';

const sogni = await SogniClient.createInstance({
  appId: 'your-unique-app-id',  // Required - UUID recommended
  network: 'fast'                // 'fast' or 'relaxed'
});
```

AUTHENTICATION - API KEY (Recommended):
```javascript
// API key auth — auto-authenticates via WebSocket, no login() needed
const sogni = await SogniClient.createInstance({
  appId: 'your-unique-app-id',
  network: 'fast',
  apiKey: 'your-api-key'
});
// Client is authenticated immediately after creation
```

AUTHENTICATION - USERNAME/PASSWORD:
```javascript
// Username/password login
const sogni = await SogniClient.createInstance({
  appId: 'your-unique-app-id',
  network: 'fast'
});
await sogni.account.login('username', 'password');
```

API KEY VS USERNAME/PASSWORD:
- API key: Pass `apiKey` to `createInstance()`. Auto-authenticates via WebSocket
  header. No `login()` call needed. Most REST API calls (balance, profile, etc.)
  available. Sensitive operations (withdrawals, staking, 2FA) not available.
- Username/password: Call `login()` after `createInstance()`. Full REST API access.

ACCOUNT INFO (Available with both auth methods):
```javascript
// Check if authenticated
const isLoggedIn = await sogni.checkAuth();

// Get current user info
const user = await sogni.account.me();
console.log(user.username, user.email);

// Get balance (username/password auth only)
await sogni.account.refreshBalance();
console.log(sogni.account.currentAccount.balance);
```

NETWORK TYPES:
- 'fast': High-end GPU workers. Faster, higher cost. REQUIRED for video.
- 'relaxed': Mac device workers. Slower, lower cost. Image only.

CONNECTION EVENTS:
```javascript
sogni.apiClient.on('connected', ({ network }) => {
  console.log('Connected to:', network);
});

sogni.apiClient.on('disconnected', ({ code, reason }) => {
  console.log('Disconnected:', code, reason);
});
```

================================================================================
4. IMAGE GENERATION
================================================================================

BASIC IMAGE GENERATION:
```javascript
const project = await sogni.projects.create({
  type: 'image',
  modelId: 'flux1-schnell-fp8',
  positivePrompt: 'A serene mountain landscape at dawn',
  negativePrompt: 'blurry, low quality, watermark',
  stylePrompt: 'photorealistic',
  numberOfMedia: 1,
  steps: 4,
  guidance: 1,
  outputFormat: 'jpg'
});

const urls = await project.waitForCompletion();
```

WITH SIZE PRESET:
```javascript
const project = await sogni.projects.create({
  type: 'image',
  modelId: 'flux1-schnell-fp8',
  positivePrompt: 'Portrait of a woman',
  numberOfMedia: 1,
  sizePreset: 'portrait_7_9',
  steps: 4,
  guidance: 1
});
```

WITH CUSTOM DIMENSIONS:
```javascript
const project = await sogni.projects.create({
  type: 'image',
  modelId: 'flux1-schnell-fp8',
  positivePrompt: 'Wide landscape',
  numberOfMedia: 1,
  sizePreset: 'custom',
  width: 1920,
  height: 1080,
  steps: 4,
  guidance: 1
});
```

IMAGE-TO-IMAGE (Starting Image):
```javascript
import fs from 'fs';

const project = await sogni.projects.create({
  type: 'image',
  modelId: 'coreml-cyberrealistic_v70_768',
  positivePrompt: 'Transform into oil painting style',
  startingImage: fs.readFileSync('./input.png'),
  startingImageStrength: 0.7,  // 0-1, higher = more original preserved
  numberOfMedia: 1,
  steps: 20,
  guidance: 7.5
});
```

WITH CONTROLNET:
```javascript
const project = await sogni.projects.create({
  type: 'image',
  modelId: 'coreml-cyberrealistic_v70_768',
  positivePrompt: 'Professional portrait photo',
  numberOfMedia: 1,
  steps: 20,
  guidance: 7.5,
  controlNet: {
    name: 'openpose',
    image: fs.readFileSync('./pose-reference.png'),
    strength: 0.8,
    mode: 'balanced',
    guidanceStart: 0,
    guidanceEnd: 1
  }
});
```

CONTROLNET TYPES:
- canny: Edge detection
- depth: Depth map
- openpose: Body pose
- lineart: Line art extraction
- lineartanime: Anime-style lines
- scribble: Rough sketches
- softedge: Soft edge detection
- tile: Texture/detail preservation
- inpaint: Inpainting mask
- instrp2p: Instruction-based editing
- mlsd: Straight line detection
- normalbae: Normal map
- segmentation: Semantic segmentation
- shuffle: Style shuffle
- instantid: Face identity

WITH CONTEXT IMAGES (Qwen Image Edit):
```javascript
const project = await sogni.projects.create({
  type: 'image',
  modelId: 'qwen_image_edit_2511_fp8_lightning',
  positivePrompt: 'Combine these into one scene',
  contextImages: [
    fs.readFileSync('./image1.png'),
    fs.readFileSync('./image2.png'),
    fs.readFileSync('./image3.png')
  ],
  numberOfMedia: 1,
  steps: 4,
  guidance: 1
});
```

RECOMMENDED MODELS:
| Model ID | Type | Steps | Guidance | Notes |
|----------|------|-------|----------|-------|
| flux1-schnell-fp8 | Fast | 4 | 1 | Best for quick iteration |
| z_image_turbo_bf16 | Ultra-fast | 8 | 1 | Fastest generation |
| z_image_bf16 | Quality | 20 | 3 | High quality Z-Image generation |
| qwen_image_edit_2511_fp8_lightning | Edit | 4 | 1 | Multi-image context |
| coreml-cyberrealistic_v70_768 | SD 1.5 | 20-40 | 7.5 | Photorealistic |

================================================================================
5. VIDEO GENERATION - WAN 2.2 MODELS
================================================================================

CRITICAL: WAN 2.2 FPS BEHAVIOR
------------------------------
WAN 2.2 models ALWAYS generate video at 16fps internally.
The 'fps' parameter (16 or 32) only controls POST-RENDER frame interpolation:
- fps: 16 -> No interpolation, output is 16fps
- fps: 32 -> Frames are doubled via interpolation, output is 32fps

Frame calculation: duration * 16 + 1 (IGNORES fps setting)
Example: 5 seconds always = 81 frames generated, regardless of fps

MODEL VARIANTS:
- Speed models (with _lightx2v suffix): 4-step, faster, good quality
- Quality models (without suffix): More steps, slower, best quality

WORKFLOW REFERENCE TABLE:
| Workflow | Model ID | Assets Required |
|----------|----------|-----------------|
| Text-to-Video | wan_v2.2-14b-fp8_t2v_lightx2v | None |
| Image-to-Video | wan_v2.2-14b-fp8_i2v_lightx2v | referenceImage |
| Sound-to-Video | wan_v2.2-14b-fp8_s2v_lightx2v | referenceImage + referenceAudio |
| Animate-Move | wan_v2.2-14b-fp8_animate-move_lightx2v | referenceImage + referenceVideo |
| Animate-Replace | wan_v2.2-14b-fp8_animate-replace_lightx2v | referenceImage + referenceVideo |

TEXT-TO-VIDEO:
```javascript
const project = await sogni.projects.create({
  type: 'video',
  network: 'fast',
  modelId: 'wan_v2.2-14b-fp8_t2v_lightx2v',
  positivePrompt: 'A hummingbird hovering near a flower, slow motion',
  negativePrompt: 'blurry, distorted',
  numberOfMedia: 1,
  duration: 5,
  fps: 16,
  shift: 5.0  // Motion intensity: 1.0-8.0
});

const urls = await project.waitForCompletion();
```

IMAGE-TO-VIDEO:
```javascript
const project = await sogni.projects.create({
  type: 'video',
  network: 'fast',
  modelId: 'wan_v2.2-14b-fp8_i2v_lightx2v',
  positivePrompt: 'The subject slowly turns their head and smiles',
  referenceImage: fs.readFileSync('./portrait.jpg'),
  numberOfMedia: 1,
  duration: 5,
  fps: 16
});
```

IMAGE-TO-VIDEO WITH END FRAME (Interpolation):
```javascript
const project = await sogni.projects.create({
  type: 'video',
  network: 'fast',
  modelId: 'wan_v2.2-14b-fp8_i2v_lightx2v',
  positivePrompt: 'Smooth transition between poses',
  referenceImage: fs.readFileSync('./start-pose.jpg'),
  referenceImageEnd: fs.readFileSync('./end-pose.jpg'),
  numberOfMedia: 1,
  duration: 5,
  fps: 16
});
```

SOUND-TO-VIDEO (Lip Sync):
```javascript
const project = await sogni.projects.create({
  type: 'video',
  network: 'fast',
  modelId: 'wan_v2.2-14b-fp8_s2v_lightx2v',
  positivePrompt: '',  // Optional for s2v
  referenceImage: fs.readFileSync('./face.jpg'),
  referenceAudio: fs.readFileSync('./speech.m4a'),
  audioStart: 0,       // Where to start in audio file (seconds)
  audioDuration: 5,    // How much audio to use (seconds)
  numberOfMedia: 1,
  duration: 5,
  fps: 16
});
```

ANIMATE-MOVE (Motion Transfer):
```javascript
const project = await sogni.projects.create({
  type: 'video',
  network: 'fast',
  modelId: 'wan_v2.2-14b-fp8_animate-move_lightx2v',
  positivePrompt: '',
  referenceImage: fs.readFileSync('./character.jpg'),    // Subject to animate
  referenceVideo: fs.readFileSync('./motion-source.mp4'), // Motion source
  videoStart: 0,  // Where to start in video (seconds)
  numberOfMedia: 1,
  duration: 5,
  fps: 16
});
```

ANIMATE-REPLACE (Subject Replacement):
```javascript
const project = await sogni.projects.create({
  type: 'video',
  network: 'fast',
  modelId: 'wan_v2.2-14b-fp8_animate-replace_lightx2v',
  positivePrompt: '',
  referenceImage: fs.readFileSync('./new-character.jpg'),  // New subject
  referenceVideo: fs.readFileSync('./original-video.mp4'), // Video with subject to replace
  videoStart: 0,
  numberOfMedia: 1,
  duration: 5,
  fps: 16
});
```

WAN 2.2 VIDEO PARAMETERS:
| Parameter | Type | Range | Default | Notes |
|-----------|------|-------|---------|-------|
| duration | number | 1-10 | - | Seconds of video |
| fps | number | 16, 32 | 16 | Only controls interpolation |
| shift | number | 1.0-8.0 | 5.0/8.0 | Motion intensity |
| steps | number | 4-50 | varies | More = quality, slower |
| teacacheThreshold | number | 0.0-1.0 | 0 | Speedup optimization |
| audioStart | number | 0+ | 0 | s2v: audio start position |
| audioDuration | number | 1-30 | 30 | s2v: audio length to use |
| videoStart | number | 0+ | 0 | animate: video start position |

================================================================================
6. VIDEO GENERATION - LTX-2 MODELS
================================================================================

CRITICAL: LTX-2 FPS BEHAVIOR (Different from WAN!)
--------------------------------------------------
LTX-2 models generate at the ACTUAL specified FPS (1-60 range).
There is NO post-render interpolation.

Frame calculation: duration * fps + 1
Frame count must follow pattern: 1 + n*8 (valid: 1, 9, 17, 25, 33, 41, ...)
The SDK automatically snaps to valid frame counts.

Example: 5 seconds at 24fps = 121 frames (1 + 15*8 = 121)

LTX-2 MODELS:
- Speed models (with _distilled suffix): 8-step, faster, good quality
- Quality models (without suffix): 20-step, slower, best quality

| Workflow | Model ID (Fast) | Model ID (Quality) |
|----------|-----------------|---------------------|
| Text-to-Video | ltx2-19b-fp8_t2v_distilled | ltx2-19b-fp8_t2v |
| Image-to-Video | ltx2-19b-fp8_i2v_distilled | ltx2-19b-fp8_i2v |
| Video-to-Video (ControlNet) | ltx2-19b-fp8_v2v_distilled | ltx2-19b-fp8_v2v |

LTX-2 TEXT-TO-VIDEO:
```javascript
const project = await sogni.projects.create({
  type: 'video',
  network: 'fast',
  modelId: 'ltx2-19b-fp8_t2v',
  positivePrompt: 'A butterfly landing on a flower',
  numberOfMedia: 1,
  duration: 8,
  fps: 24
});
```

LTX-2 IMAGE-TO-VIDEO WITH KEYFRAMES:
```javascript
const project = await sogni.projects.create({
  type: 'video',
  network: 'fast',
  modelId: 'ltx2-19b-fp8_i2v',
  positivePrompt: 'Smooth camera movement',
  referenceImage: fs.readFileSync('./start.jpg'),
  referenceImageEnd: fs.readFileSync('./end.jpg'),
  firstFrameStrength: 0.6,  // 0-1, how closely to match start
  lastFrameStrength: 0.6,   // 0-1, how closely to match end
  numberOfMedia: 1,
  duration: 8,
  fps: 24
});
```

LTX-2 VIDEO-TO-VIDEO WITH CONTROLNET:
```javascript
const project = await sogni.projects.create({
  type: 'video',
  network: 'fast',
  modelId: 'ltx2-19b-fp8_v2v_distilled',  // Must use v2v model for ControlNet
  positivePrompt: 'Transform into anime style',
  referenceVideo: fs.readFileSync('./source-video.mp4'),
  controlNet: {
    name: 'canny',  // 'canny', 'pose', 'depth', or 'detailer'
    strength: 0.6   // Recommended: canny/depth 0.6, pose/detailer 0.85
  },
  numberOfMedia: 1,
  duration: 5,
  fps: 24
});
```

LTX-2 VIDEO PARAMETERS:
| Parameter | Type | Range | Default | Notes |
|-----------|------|-------|---------|-------|
| duration | number | 4-20 | - | Seconds of video |
| fps | number | 1-60 | 24 | Actual generation FPS |
| firstFrameStrength | number | 0.0-1.0 | 0.6 | Keyframe matching |
| lastFrameStrength | number | 0.0-1.0 | 0.6 | Keyframe matching |

================================================================================
7. AUDIO GENERATION - ACE-STEP 1.5 MODELS
================================================================================

ACE-Step 1.5 models generate music from text descriptions with optional lyrics.
Two model variants are available:

MODEL VARIANTS:
| Model ID | Name | Description |
|----------|------|-------------|
| ace_step_1.5_turbo | Fast & Catchy | Quick generation, best quality sound |
| ace_step_1.5_sft | More Control | More accurate lyrics, less stable |

TEXT-TO-MUSIC (Instrumental):
```javascript
const project = await sogni.projects.create({
  type: 'audio',
  modelId: 'ace_step_1.5_turbo',
  positivePrompt: 'Upbeat electronic dance music with synth leads and driving bass',
  numberOfMedia: 1,
  duration: 30,       // seconds (10-600)
  bpm: 128,           // beats per minute (30-300)
  keyscale: 'C major',
  timesignature: '4', // 4/4 time
  steps: 8,
  outputFormat: 'mp3'
});

const urls = await project.waitForCompletion();
console.log(urls[0]); // Audio URL (valid 24 hours)
```

TEXT-TO-MUSIC (With Lyrics):
```javascript
const project = await sogni.projects.create({
  type: 'audio',
  modelId: 'ace_step_1.5_sft',
  positivePrompt: 'Soft acoustic folk ballad with fingerpicked guitar',
  lyrics: 'Verse 1:\nWalking down a quiet road\nWith the wind upon my face\n\nChorus:\nThis is where I find my peace\nIn this gentle, open space',
  language: 'en',
  numberOfMedia: 2,   // Generate 2 versions
  duration: 60,
  bpm: 90,
  keyscale: 'A minor',
  timesignature: '3', // 3/4 waltz time
  steps: 12,
  composerMode: true,
  creativity: 0.85,
  promptStrength: 2.0
});
```

MULTIPLE VERSIONS:
Use `numberOfMedia` (1-4) to generate multiple variations of the same prompt.
Each version uses a different random seed, producing unique interpretations.

ACE-STEP 1.5 AUDIO PARAMETERS:
| Parameter | Type | Range | Default | Notes |
|-----------|------|-------|---------|-------|
| duration | number | 10-600 | 30 | Seconds of audio |
| bpm | number | 30-300 | 120 | Beats per minute |
| keyscale | string | see below | C major | Musical key and scale |
| timesignature | string | 2, 3, 4, 6 | 4 | Time signature (2/4, 3/4, 4/4, 6/8) |
| lyrics | string | - | - | Song lyrics (omit for instrumental) |
| language | string | en, ja, zh, etc. | en | Lyrics language code (51 languages) |
| composerMode | boolean | - | true | AI composer for higher quality |
| promptStrength | number | 0-10 | 2.0 | Prompt adherence (higher = stricter) |
| creativity | number | 0-2 | 0.85 | Composition temperature |
| shift | number | 1-5 | 3 | Denoising distribution (turbo only) |
| steps | number | 4-16 | 8 | Inference steps |
| outputFormat | string | mp3, wav, flac | mp3 | Audio output format |

SUPPORTED KEYS:
C, C#, Db, D, D#, Eb, E, F, F#, Gb, G, G#, Ab, A, A#, Bb, B
(each with major and minor variants, e.g., "C major", "A minor")

TIME SIGNATURES:
- 2 = 2/4 time (marches, polka)
- 3 = 3/4 time (waltzes, ballads)
- 4 = 4/4 time (most pop, rock, hip-hop)
- 6 = 6/8 time (compound time, folk dances)

SUPPORTED LANGUAGES:
en (English), ja (Japanese), zh (Chinese), es (Spanish), de (German),
fr (French), pt (Portuguese), ru (Russian), it (Italian), nl (Dutch),
pl (Polish), tr (Turkish), vi (Vietnamese), cs (Czech), fa (Persian),
id (Indonesian), ko (Korean), uk (Ukrainian), hu (Hungarian), ar (Arabic),
sv (Swedish), ro (Romanian), el (Greek), and more. Use 'unknown' for auto-detect.

================================================================================
8. LLM TEXT GENERATION
================================================================================

The Sogni SDK supports LLM text generation through the Supernet, providing an
OpenAI-compatible chat completions API with streaming, multi-turn conversations,
thinking/reasoning mode, and tool calling.

DEFAULT LLM MODEL: qwen3-30b-a3b-gptq-int4

CHAT COMPLETION (Non-Streaming):
```javascript
const response = await sogni.projects.chatCompletion({
  model: 'qwen3-30b-a3b-gptq-int4',
  messages: [
    { role: 'system', content: 'You are a helpful assistant.' },
    { role: 'user', content: 'Explain quantum computing briefly.' }
  ],
  max_tokens: 4096,
  temperature: 0.7,
  top_p: 0.9
});

console.log(response.choices[0].message.content);
```

STREAMING CHAT COMPLETION:
```javascript
const stream = await sogni.projects.chatCompletionStream({
  model: 'qwen3-30b-a3b-gptq-int4',
  messages: [{ role: 'user', content: 'Tell me a story about a cat' }],
  max_tokens: 4096,
  stream: true
});

for await (const chunk of stream) {
  const content = chunk.choices[0].delta.content || '';
  process.stdout.write(content);
}
```

MULTI-TURN CONVERSATION:
```javascript
const messages = [
  { role: 'system', content: 'You are a helpful assistant.' }
];

// Turn 1
messages.push({ role: 'user', content: 'What is the Sogni Supernet?' });
const response1 = await sogni.projects.chatCompletion({
  model: 'qwen3-30b-a3b-gptq-int4',
  messages
});
messages.push(response1.choices[0].message);

// Turn 2 (with context from turn 1)
messages.push({ role: 'user', content: 'How does it compare to centralized services?' });
const response2 = await sogni.projects.chatCompletion({
  model: 'qwen3-30b-a3b-gptq-int4',
  messages
});
```

THINKING MODE:
Enable model reasoning/thinking via `chat_template_kwargs`:
- When enabled, the model outputs `<think>...</think>` blocks showing its reasoning
- When disabled (default), the model provides direct responses

LLM CHAT PARAMETERS:
| Parameter | Type | Default | Notes |
|-----------|------|---------|-------|
| model | string | qwen3-30b-a3b-gptq-int4 | LLM model ID |
| messages | array | - | Chat messages [{role, content}] |
| max_tokens | number | 4096 | Maximum output tokens |
| temperature | number | 0.7 | Sampling temperature (0-2) |
| top_p | number | 0.9 | Nucleus sampling (0-1) |
| frequency_penalty | number | 0 | Repetition penalty (-2 to 2) |
| presence_penalty | number | 0 | Topic freshness penalty (-2 to 2) |
| stream | boolean | false | Enable token-by-token streaming |
| tools | array | - | Tool definitions for function calling |
| tool_choice | string | auto | Tool selection: 'auto', 'none', or specific |

MESSAGE ROLES:
- 'system': System instructions (first message)
- 'user': User input
- 'assistant': Model responses (and tool call requests)
- 'tool': Tool execution results (with tool_call_id)

================================================================================
9. LLM TOOL CALLING & SOGNI PLATFORM TOOLS
================================================================================

TOOL CALLING (Function Calling):
Define custom tools the LLM can invoke during conversations. The LLM decides
when to use a tool, returns structured arguments, and you execute the function
locally before feeding results back for a natural language answer.

DEFINING TOOLS (OpenAI-compatible format):
```javascript
const tools = [
  {
    type: 'function',
    function: {
      name: 'get_weather',
      description: 'Get current weather for a location',
      parameters: {
        type: 'object',
        properties: {
          location: { type: 'string', description: 'City name or location' }
        },
        required: ['location']
      }
    }
  },
  {
    type: 'function',
    function: {
      name: 'calculate',
      description: 'Evaluate a math expression',
      parameters: {
        type: 'object',
        properties: {
          expression: { type: 'string', description: 'Math expression to evaluate' }
        },
        required: ['expression']
      }
    }
  }
];
```

TOOL CALLING FLOW:
```javascript
// 1. Send message with tools
const response = await sogni.projects.chatCompletion({
  model: 'qwen3-30b-a3b-gptq-int4',
  messages: [{ role: 'user', content: "What's the weather in Austin, TX?" }],
  tools,
  tool_choice: 'auto'
});

// 2. Check if LLM wants to call a tool
const message = response.choices[0].message;
if (message.tool_calls) {
  for (const toolCall of message.tool_calls) {
    const args = JSON.parse(toolCall.function.arguments);

    // 3. Execute the tool locally
    const result = await executeMyTool(toolCall.function.name, args);

    // 4. Feed result back
    messages.push(message); // assistant message with tool_calls
    messages.push({
      role: 'tool',
      tool_call_id: toolCall.id,
      content: JSON.stringify(result)
    });
  }

  // 5. Get final natural language response
  const finalResponse = await sogni.projects.chatCompletion({
    model: 'qwen3-30b-a3b-gptq-int4',
    messages,
    tools
  });
}
```

MULTI-ROUND TOOL CALLING:
The LLM may call multiple tools in sequence (e.g., get weather for two cities).
Implement a loop that continues until the LLM returns a content response
without tool calls, typically up to 5 rounds.

SOGNI PLATFORM TOOLS — MEDIA GENERATION VIA CHAT:
Combine LLM intelligence with Sogni's media generation capabilities. The LLM
detects when a user wants to create media, enhances the prompt, and calls
Sogni's generation APIs automatically:

- Image Generation: "Create an image of a cyberpunk city at night"
- Video Generation: "Generate a video of ocean waves crashing at sunset"
- Music Generation: "Compose a jazz song about the rain"

DEFAULT GENERATION MODELS (used by platform tools):
| Media Type | Model ID | Description |
|------------|----------|-------------|
| Image | z_image_turbo_bf16 | Z-Image Turbo, ultra-fast 8-step generation |
| Video | ltx2-19b-fp8_t2v_distilled | LTX-2 text-to-video, fast distilled |
| Audio | ace_step_1.5_turbo | ACE-Step 1.5 Turbo, fast music generation |

PLATFORM TOOL WORKFLOW:
1. User sends natural language request (e.g., "Make me an image of a dragon")
2. LLM detects media generation intent
3. LLM enhances the prompt for optimal output quality
4. LLM calls the appropriate Sogni generation tool with structured parameters
5. Your code executes the Sogni project creation (image/video/audio)
6. Result URL is returned and opened automatically

See `workflow_text_chat_sogni_tools.mjs` for the complete implementation with
image, video, and music generation tools wired to Sogni's Projects API.

EXAMPLE CLI USAGE:
```bash
# Generate an image through natural language
node workflow_text_chat_sogni_tools.mjs "Create an image of a cyberpunk city"

# Generate a video through natural language
node workflow_text_chat_sogni_tools.mjs "Generate a video of ocean waves at sunset"

# Generate music through natural language
node workflow_text_chat_sogni_tools.mjs "Compose a jazz song about the rain"

# Generate multiple items
node workflow_text_chat_sogni_tools.mjs -n 4 "Create images of fantasy landscapes"

# Tool calling with external tools (weather, time, math, conversions)
node workflow_text_chat_tool_calling.mjs "What's the weather in Austin, TX?"
```

================================================================================
10. COMPLETE PARAMETER REFERENCE
================================================================================

BASE PARAMETERS (Both Image and Video):
```typescript
{
  modelId: string;           // Required - model identifier
  numberOfMedia: number;     // Required - how many to generate
  positivePrompt: string;    // Required - what to create
  negativePrompt?: string;   // What to avoid
  stylePrompt?: string;      // Style description
  steps?: number;            // Inference steps
  guidance?: number;         // Guidance scale
  network?: 'fast' | 'relaxed';
  seed?: number;             // Reproducibility seed
  tokenType?: 'sogni' | 'spark';
  disableNSFWFilter?: boolean;
  loras?: string[];          // LoRA IDs
  loraStrengths?: number[];  // LoRA weights (0-2)
}
```

IMAGE-SPECIFIC PARAMETERS:
```typescript
{
  type: 'image';
  sizePreset?: string;       // e.g., 'square_hd', 'portrait_7_9'
  width?: number;            // If sizePreset is 'custom'
  height?: number;           // If sizePreset is 'custom'
  sampler?: string;          // Model-specific
  scheduler?: string;        // Model-specific
  numberOfPreviews?: number; // Preview images during generation
  startingImage?: File | Buffer | Blob;
  startingImageStrength?: number;  // 0-1
  contextImages?: (File | Buffer | Blob)[];
  controlNet?: ControlNetParams;
  outputFormat?: 'png' | 'jpg';
}
```

VIDEO-SPECIFIC PARAMETERS:
```typescript
{
  type: 'video';
  duration?: number;         // Seconds (preferred over frames)
  frames?: number;           // Deprecated - use duration
  fps?: number;              // Model-dependent behavior!
  shift?: number;            // Motion intensity (1.0-8.0)
  teacacheThreshold?: number;// Speedup (0.0-1.0)
  width?: number;
  height?: number;
  sampler?: string;
  scheduler?: string;
  referenceImage?: File | Buffer | Blob;
  referenceImageEnd?: File | Buffer | Blob;
  referenceAudio?: File | Buffer | Blob;
  referenceVideo?: File | Buffer | Blob;
  audioStart?: number;       // Seconds into audio
  audioDuration?: number;    // Seconds of audio
  videoStart?: number;       // Seconds into video
  firstFrameStrength?: number;  // LTX-2 only (0-1)
  lastFrameStrength?: number;   // LTX-2 only (0-1)
  trimEndFrame?: boolean;
  controlNet?: VideoControlNetParams;  // LTX-2 only
  outputFormat?: 'mp4';
}
```

AUDIO-SPECIFIC PARAMETERS:
```typescript
{
  type: 'audio';
  duration?: number;           // 10-600 seconds
  bpm?: number;                // 30-300 beats per minute
  keyscale?: string;           // e.g., 'C major', 'A minor'
  timesignature?: string;      // '2', '3', '4', or '6'
  lyrics?: string;             // Song lyrics (omit for instrumental)
  language?: string;           // Language code (default: 'en')
  composerMode?: boolean;      // AI composer mode (default: true)
  promptStrength?: number;     // 0-10, prompt adherence
  creativity?: number;         // 0-2, composition temperature
  shift?: number;              // 1-5, denoising distribution
  sampler?: string;            // Model-specific
  scheduler?: string;          // Model-specific
  outputFormat?: 'mp3' | 'wav' | 'flac';
}
```

================================================================================
11. EVENTS & PROGRESS TRACKING
================================================================================

PROMISE-BASED COMPLETION:
```javascript
const project = await sogni.projects.create({ ... });
const urls = await project.waitForCompletion();
// urls is string[] of result URLs
```

PROJECT EVENTS:
```javascript
project.on('progress', (percent) => {
  console.log(`Overall: ${percent}%`);
});

project.on('jobStarted', (job) => {
  console.log(`Job ${job.id} started on ${job.workerName}`);
});

project.on('jobCompleted', (job) => {
  console.log(`Job ${job.id} done:`, job.resultUrl);
});

project.on('jobFailed', (job) => {
  console.log(`Job ${job.id} failed:`, job.error);
});

project.on('completed', (urls) => {
  console.log('All done:', urls);
});

project.on('failed', (error) => {
  console.error('Project failed:', error.message);
});
```

JOB EVENTS:
```javascript
const job = project.jobs[0];

job.on('progress', ({ step, stepCount, progress }) => {
  console.log(`Step ${step}/${stepCount} (${progress}%)`);
});

job.on('preview', (previewUrl) => {
  console.log('Preview available:', previewUrl);
});

job.on('completed', () => {
  console.log('Result:', job.resultUrl);
});

job.on('failed', (error) => {
  console.error('Job failed:', error);
});
```

PROJECT PROPERTIES:
```javascript
project.id           // string
project.status       // 'pending' | 'queued' | 'processing' | 'completed' | 'failed' | 'canceled'
project.progress     // number (0-100)
project.queuePosition // number | null
project.eta          // number | null (seconds)
project.jobs         // Job[]
project.resultUrls   // string[] (after completion)
```

JOB PROPERTIES:
```javascript
job.id               // string
job.status           // 'pending' | 'initiating' | 'processing' | 'completed' | 'failed' | 'canceled'
job.progress         // number (0-100)
job.step             // number (current step)
job.stepCount        // number (total steps)
job.seed             // number
job.previewUrl       // string | null
job.resultUrl        // string | null
job.hasResultMedia   // boolean
job.isNSFW           // boolean
job.workerName       // string
job.eta              // string | null
job.etaSeconds       // number | null
job.type             // 'image' | 'video'
```

FETCHING RESULTS:
```javascript
// Get signed URL (valid ~1 hour)
const signedUrl = await job.getResultUrl();

// Download as blob
const blob = await job.getResultData();
```

================================================================================
12. MODELS, PRESETS & OPTIONS
================================================================================

WAIT FOR MODELS:
```javascript
const models = await sogni.projects.waitForModels();
// Returns AvailableModel[]
```

GET AVAILABLE MODELS:
```javascript
const models = sogni.projects.availableModels;
// or
const models = await sogni.projects.getAvailableModels('fast');
// Returns: { id, name, workerCount, media: 'image' | 'video' }[]
```

GET ALL SUPPORTED MODELS:
```javascript
const allModels = await sogni.projects.getSupportedModels();
// Returns full model list with tiers
```

GET SIZE PRESETS:
```javascript
const presets = await sogni.projects.getSizePresets('fast', 'flux1-schnell-fp8');
/*
Returns: [
  { id: 'square', label: 'Square', width: 512, height: 512, ratio: '1:1' },
  { id: 'square_hd', label: 'Square HD', width: 1024, height: 1024, ratio: '1:1' },
  { id: 'portrait_7_9', label: 'Portrait: Standard', width: 896, height: 1152, ratio: '7:9' },
  { id: 'landscape_7_4', label: 'Landscape: Widescreen', width: 1344, height: 768, ratio: '7:4' },
  ...
]
*/
```

GET SAMPLER/SCHEDULER OPTIONS:
```javascript
const options = await sogni.projects.getModelOptions('flux1-schnell-fp8');
/*
Returns: {
  sampler: {
    allowed: ['euler', 'euler_a', 'dpm_pp_2m', ...],
    default: 'euler'
  },
  scheduler: {
    allowed: ['simple', 'karras', 'linear', ...],
    default: 'simple'
  }
}
*/
```

================================================================================
13. COST ESTIMATION
================================================================================

ESTIMATE IMAGE COST:
```javascript
const cost = await sogni.projects.estimate({
  network: 'fast',
  tokenType: 'sogni',
  model: 'flux1-schnell-fp8',
  imageCount: 4,
  stepCount: 4,
  previewCount: 0,
  sizePreset: 'square_hd'
});

console.log(cost.sogni);  // Cost in Sogni tokens
console.log(cost.spark);  // Cost in Spark tokens
console.log(cost.usd);    // Cost in USD
```

ESTIMATE VIDEO COST:
```javascript
const cost = await sogni.projects.estimateVideo({
  tokenType: 'sogni',
  model: 'wan_v2.2-14b-fp8_t2v_lightx2v',
  width: 512,
  height: 512,
  duration: 5,
  fps: 16,
  steps: 4,
  numberOfMedia: 1
});
```

CHECK BALANCE:
```javascript
await sogni.account.refreshBalance();
const balance = sogni.account.currentAccount.balance;
console.log(balance.sogni, balance.spark);
```

================================================================================
14. ERROR HANDLING
================================================================================

TRY-CATCH PATTERN:
```javascript
try {
  const project = await sogni.projects.create({ ... });
  const urls = await project.waitForCompletion();
} catch (error) {
  if (error.code) {
    // API error with code
    console.error(`Error ${error.code}: ${error.message}`);
  } else {
    // Network or other error
    console.error(error.message);
  }
}
```

EVENT-BASED ERROR HANDLING:
```javascript
project.on('failed', (error) => {
  console.error('Project failed:', error.code, error.message);
});

project.on('jobFailed', (job) => {
  console.error('Job failed:', job.error);
});
```

CANCEL A PROJECT:
```javascript
await sogni.projects.cancel(project.id);
// or
await project.cancel();
```

COMMON ERROR SCENARIOS:
- Insufficient balance
- Invalid model ID
- Missing required parameters (e.g., referenceImage for i2v)
- Network unavailable
- NSFW content detected (if filter enabled)
- Invalid dimensions or parameters

================================================================================
15. ADVANCED PATTERNS
================================================================================

BATCH PROCESSING:
```javascript
const images = ['image1.jpg', 'image2.jpg', 'image3.jpg'];
const projects = [];

for (const img of images) {
  const project = await sogni.projects.create({
    type: 'video',
    network: 'fast',
    modelId: 'wan_v2.2-14b-fp8_i2v_lightx2v',
    referenceImage: fs.readFileSync(img),
    positivePrompt: 'camera slowly zooms in',
    numberOfMedia: 1,
    duration: 5,
    fps: 16
  });
  projects.push(project);
}

const results = await Promise.all(
  projects.map(p => p.waitForCompletion())
);
```

IMAGE ENHANCEMENT:
```javascript
// After a job completes, enhance it
const enhancedProject = await job.enhance('medium', {
  // Optional overrides
  steps: 30
});
const enhancedUrls = await enhancedProject.waitForCompletion();
```

TOKEN MANAGEMENT:
```javascript
// Use Spark tokens instead of Sogni
const project = await sogni.projects.create({
  type: 'image',
  tokenType: 'spark',
  // ... other params
});
```

USING LORAS:
```javascript
const project = await sogni.projects.create({
  type: 'image',
  modelId: 'flux1-schnell-fp8',
  positivePrompt: 'Portrait from multiple angles',
  loras: ['multiple_angles'],
  loraStrengths: [0.9],
  numberOfMedia: 1
});
```

================================================================================
16. TYPE DEFINITIONS
================================================================================

PROJECT PARAMS:
```typescript
type ProjectParams = ImageProjectParams | VideoProjectParams;

interface BaseProjectParams {
  modelId: string;
  numberOfMedia: number;
  positivePrompt: string;
  negativePrompt?: string;
  stylePrompt?: string;
  steps?: number;
  guidance?: number;
  network?: 'fast' | 'relaxed';
  disableNSFWFilter?: boolean;
  seed?: number;
  tokenType?: 'sogni' | 'spark';
  loras?: string[];
  loraStrengths?: number[];
}

interface ImageProjectParams extends BaseProjectParams {
  type: 'image';
  numberOfPreviews?: number;
  startingImage?: File | Buffer | Blob;
  startingImageStrength?: number;
  contextImages?: (File | Buffer | Blob)[];
  sampler?: string;
  scheduler?: string;
  sizePreset?: string | 'custom';
  width?: number;
  height?: number;
  controlNet?: ControlNetParams;
  outputFormat?: 'png' | 'jpg';
}

interface VideoProjectParams extends BaseProjectParams {
  type: 'video';
  frames?: number;  // deprecated
  duration?: number;
  fps?: number;
  shift?: number;
  teacacheThreshold?: number;
  referenceImage?: File | Buffer | Blob;
  referenceImageEnd?: File | Buffer | Blob;
  referenceAudio?: File | Buffer | Blob;
  referenceVideo?: File | Buffer | Blob;
  audioStart?: number;
  audioDuration?: number;
  videoStart?: number;
  trimEndFrame?: boolean;
  width?: number;
  height?: number;
  sampler?: string;
  scheduler?: string;
  firstFrameStrength?: number;
  lastFrameStrength?: number;
  outputFormat?: 'mp4';
  controlNet?: VideoControlNetParams;
}
```

CONTROLNET TYPES:
```typescript
type ControlNetName =
  | 'canny' | 'depth' | 'inpaint' | 'instrp2p'
  | 'lineart' | 'lineartanime' | 'mlsd' | 'normalbae'
  | 'openpose' | 'scribble' | 'segmentation'
  | 'shuffle' | 'softedge' | 'tile' | 'instantid';

type ControlNetMode = 'balanced' | 'prompt_priority' | 'cn_priority';

interface ControlNetParams {
  name: ControlNetName;
  image?: File | Buffer | Blob;
  strength?: number;
  mode?: ControlNetMode;
  guidanceStart?: number;
  guidanceEnd?: number;
}

type VideoControlNetName = 'canny' | 'pose' | 'depth' | 'detailer';

interface VideoControlNetParams {
  name: VideoControlNetName;
  strength?: number;
}
```

MODEL TYPES:
```typescript
interface AvailableModel {
  id: string;
  name: string;
  workerCount: number;
  media: 'image' | 'video';
}

interface SupportedModel extends AvailableModel {
  SID: number;
  tier: string;
}

interface SizePreset {
  id: string;
  label: string;
  width: number;
  height: number;
  ratio: string;
  aspect: string;
}
```

STATUS TYPES:
```typescript
type ProjectStatus =
  | 'pending' | 'queued' | 'processing'
  | 'completed' | 'failed' | 'canceled';

type JobStatus =
  | 'pending' | 'initiating' | 'processing'
  | 'completed' | 'failed' | 'canceled';
```

ERROR TYPE:
```typescript
interface ErrorData {
  code: number;
  message: string;
}
```

================================================================================
END OF DOCUMENT
================================================================================

For additional resources:
- Examples: https://github.com/Sogni-AI/sogni-client/tree/main/examples
- API Docs: https://sdk-docs.sogni.ai
- Support: https://www.sogni.ai/support
