npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vidtory/ai-sdk

v1.1.0

Published

Vidtory AI SDK — generate text, images, video, and audio with a clean, type-safe API

Readme

Vidtory AI SDK


✨ Features

  • Google AI SDK Style — Modular interface: ai.models, ai.jobs, ai.media, ai.voices
  • Zero Production Dependencies — Uses native globalThis.fetch, no bloat
  • Automated Polling — Async jobs resolve automatically with progress callbacks
  • Retry with Exponential Backoff — Auto-recover from transient errors (429, 500, 502, 503, 504)
  • AbortController Support — Cancel any generation or polling operation
  • Per-Request Timeout — Prevent hung connections from blocking your app
  • Debug Logging — Transparent request/response logging for development
  • Middleware Hooks — onRequest / onResponse hooks for custom logic
  • Dual CJS/ESM — Works everywhere: Node.js, Next.js, Vite, Deno, Edge, etc.
  • TypeScript First — Full type safety with autocomplete from Vidtory's Swagger schemas
  • Request ID Tracking — Every request tagged with a unique ID for debugging

📦 Installation

npm install @vidtory/ai-sdk

🚀 Quick Start

1. Initialize the client

import { VidtoryAI } from '@vidtory/ai-sdk';

const ai = new VidtoryAI({
  apiKey: process.env.VIDTORY_API_KEY,
});

2. Generate text

const response = await ai.models.generateText({
  prompt: 'Write a catchy headline for an organic coffee brand.',
});

console.log(response.result);
// → "Wake Up to Nature's Best Brew — Pure, Organic, Unforgettable."

3. Generate an image

const response = await ai.models.generateImage({
  prompt: 'A sleek ceramic mug on a wooden table, morning light, photorealistic',
  aspectRatio: 'IMAGE_ASPECT_RATIO_LANDSCAPE',
  resolution: '4K',
});

console.log(response.result);
// → "https://b2b.vidtory.net/.../image.jpg"

4. Generate a video

const response = await ai.models.generateVideo({
  prompt: 'Coffee pouring in slow motion, steam rising, macro lens',
  duration: 8,
  aspectRatio: 'VIDEO_ASPECT_RATIO_LANDSCAPE',
});

console.log(response.result);
// → "https://b2b.vidtory.net/.../video.mp4"

5. Generate audio (TTS)

const response = await ai.models.generateAudio({
  prompt: 'Xin chào, tôi là trợ lý ảo Vidtory.',
  voiceId: 'tuan-voice-id',
  languageCode: 'vi',
});

console.log(response.result);
// → "https://b2b.vidtory.net/.../audio.mp3"

⚙️ Client Configuration

const ai = new VidtoryAI({
  // ─── Authentication ───────────────────────────────────────
  apiKey: 'vidtory_xxx',               // Or set VIDTORY_API_KEY env var
  baseURL: 'https://bapi.vidtory.net', // Or set VIDTORY_BASE_URL env var

  // ─── Retry & Resilience ───────────────────────────────────
  maxRetries: 3,                       // Auto-retry on transient errors (default: 3)
  retryDelay: 500,                     // Base delay in ms (exponential backoff) (default: 500)
  retryOn: [429, 500, 502, 503, 504], // HTTP status codes to retry on

  // ─── Timeout ──────────────────────────────────────────────
  timeout: 30_000,                     // Per-request HTTP timeout in ms (default: 30s)

  // ─── Debug ────────────────────────────────────────────────
  debug: true,                         // Or set VIDTORY_DEBUG=true env var

  // ─── Custom Headers ───────────────────────────────────────
  httpHeaders: {
    'X-Custom-Tag': 'my-app',
  },

  // ─── Custom Fetch ─────────────────────────────────────────
  fetch: customFetchFn,                // Inject custom fetch (testing, proxy, edge)

  // ─── Middleware Hooks ─────────────────────────────────────
  onRequest: (url, init) => {
    // Modify request before it's sent
    const headers = new Headers(init.headers);
    headers.set('X-Trace-ID', crypto.randomUUID());
    return { ...init, headers };
  },
  onResponse: (response, url) => {
    // Log or track responses
    console.log(`${url} → ${response.status}`);
  },
});

Configuration Options Reference

| Option | Type | Default | Description | |---|---|---|---| | apiKey | string | env.VIDTORY_API_KEY | API key for authentication | | baseURL | string | 'https://bapi.vidtory.net' | Base URL of the API | | maxRetries | number | 3 | Max retries for transient errors | | retryDelay | number | 500 | Base retry delay in ms | | retryOn | number[] | [429,500,502,503,504] | Retryable HTTP status codes | | timeout | number | 30000 | Per-request timeout in ms | | debug | boolean | false | Enable debug logging to stderr | | httpHeaders | Record<string, string> | {} | Custom headers for all requests | | fetch | typeof fetch | globalThis.fetch | Custom fetch implementation | | onRequest | Function | — | Hook before each request | | onResponse | Function | — | Hook after each response |


🔄 Polling & Async Control

All generative methods (generateText, generateImage, generateVideo, generateAudio, upscale) accept a second parameter for fine-grained polling control:

const response = await ai.models.generateImage(
  { prompt: 'A futuristic floating city' },
  {
    // ─── Polling Control ────────────────────────────────────
    awaitResult: true,        // true = wait for completion (default)
    pollIntervalMs: 3000,     // Check every 3s (default: 2000)
    timeoutMs: 600_000,       // Timeout after 10 min (default: 5 min)

    // ─── Progress Tracking ──────────────────────────────────
    onProgress: (status) => {
      console.log(`Status: ${status}`); // 'PENDING' | 'PROCESSING' | 'COMPLETED'
    },

    // ─── Cancellation ───────────────────────────────────────
    signal: controller.signal, // AbortSignal for cancellation
  }
);

Fire-and-Forget Mode

Skip automatic polling to manage jobs manually:

const response = await ai.models.generateImage(
  { prompt: 'An astronaut on Mars' },
  { awaitResult: false }
);

console.log('Job ID:', response.data.generationHistoryId);
// → Check status later with ai.jobs.getStatus()

🛑 Cancellation (AbortController)

Cancel any in-flight generation or polling loop:

const controller = new AbortController();

const promise = ai.models.generateVideo(
  { prompt: 'A cinematic sunset timelapse' },
  { signal: controller.signal }
);

// Cancel after 30 seconds
setTimeout(() => controller.abort(), 30_000);

try {
  const result = await promise;
} catch (error) {
  if (error instanceof VidtoryAbortError) {
    console.log('Generation was cancelled.');
  }
}

🔁 Automatic Retry

The SDK automatically retries transient errors with exponential backoff + jitter:

Attempt 1: immediate
Attempt 2: ~500ms + jitter
Attempt 3: ~1000ms + jitter
Attempt 4: ~2000ms + jitter

For HTTP 429 (Rate Limited), the SDK respects the Retry-After header from the server.

Disable retries for specific use cases:

const ai = new VidtoryAI({
  apiKey: 'xxx',
  maxRetries: 0, // No automatic retries
});

📁 Media Management

Upload a reference image

import fs from 'fs';

const media = await ai.media.upload({
  file: fs.readFileSync('./my-character.png'),
  fileName: 'my-character.png',
  metadata: { category: 'character', name: 'barista' },
});

console.log('Media ID:', media.id);
console.log('Media URL:', media.url);

Use uploaded media as reference

const video = await ai.models.generateVideo({
  prompt: 'A barista making latte art',
  mode: 'i2v',
  refImageUrl: media.url,
});

Full CRUD operations

// List with metadata filter
const files = await ai.media.list({
  metadata: { category: 'character' },
});

// Get by ID
const file = await ai.media.get('media-uuid');

// Update metadata
await ai.media.updateMetadata('media-uuid', {
  category: 'hero',
  verified: true,
});

// Delete
await ai.media.delete('media-uuid');

📊 Jobs & Usage Tracking

Check job status

const status = await ai.jobs.getStatus('generation-history-id');

if (status.data.status === 'COMPLETED') {
  console.log('Output URL:', status.data.result.url);
}

List jobs with filters

const jobs = await ai.jobs.list({
  status: 'COMPLETED',
  type: 'image',
  limit: 20,
  offset: 0,
});

console.log(`${jobs.total} total jobs`);

Auto-paginate all jobs

for await (const job of ai.jobs.listAll({ status: 'COMPLETED' })) {
  console.log(job.id, job.type, job.status);
}

Usage statistics

const stats = await ai.jobs.getStats('2026-01-01', '2026-01-31');

console.log('Total requests:', stats.data.totalRequests);
console.log('Credits used:', stats.data.totalCostInCredits);
console.log('Balance:', stats.data.currentBalance);

🎙️ Text-to-Speech Voices

// List all voices
const allVoices = await ai.voices.list();

// Filter by language
const viVoices = await ai.voices.list({ language: 'vi' });

console.log(`${viVoices.data.length} Vietnamese voices available`);
viVoices.data.forEach((v) => {
  console.log(`  ${v.name} (${v.gender}) — ${v.descriptive}`);
});

🔍 Debug Logging

Enable detailed request/response logging:

const ai = new VidtoryAI({
  apiKey: 'xxx',
  debug: true, // or set VIDTORY_DEBUG=true
});

Output:

[VidtoryAI 2026-05-24T06:30:00.000Z] POST /generative-core/text → 200 (1234ms)
[VidtoryAI 2026-05-24T06:30:02.000Z] GET /generative-core/jobs/abc-123/status → 200 (245ms)
[VidtoryAI 2026-05-24T06:30:02.000Z] Poll #1 for abc-123: PENDING
[VidtoryAI 2026-05-24T06:30:04.000Z] Retry attempt 1/3 after 502ms

❌ Error Handling

The SDK provides a structured error hierarchy:

import {
  VidtoryAI,
  VidtoryAPIError,        // Non-2xx HTTP responses
  VidtoryRateLimitError,  // HTTP 429 (extends VidtoryAPIError)
  VidtoryJobFailedError,  // Job status = FAILED
  VidtoryTimeoutError,    // Polling timeout exceeded
  VidtoryAbortError,      // Request cancelled via AbortSignal
} from '@vidtory/ai-sdk';

try {
  const result = await ai.models.generateImage({ prompt: '...' });
} catch (error) {
  if (error instanceof VidtoryRateLimitError) {
    console.error(`Rate limited. Retry after ${error.retryAfter}s`);

  } else if (error instanceof VidtoryAPIError) {
    console.error(`API Error ${error.statusCode}: ${error.message}`);
    console.error('Request ID:', error.requestId);
    console.error('Response:', error.responseData);

  } else if (error instanceof VidtoryJobFailedError) {
    console.error('Job failed:', error.message);
    console.error('Job state:', error.job);

  } else if (error instanceof VidtoryTimeoutError) {
    console.error('Timed out. Job ID:', error.generationHistoryId);

  } else if (error instanceof VidtoryAbortError) {
    console.log('Request was cancelled.');
  }
}

Error Class Reference

| Error Class | When Thrown | Key Properties | |---|---|---| | VidtoryAPIError | Non-2xx HTTP response | statusCode, responseData, requestId | | VidtoryRateLimitError | HTTP 429 | retryAfter (seconds) | | VidtoryJobFailedError | Job status = FAILED | job (full Job object) | | VidtoryTimeoutError | Polling exceeds timeout | generationHistoryId | | VidtoryAbortError | AbortSignal triggered | — |


📖 Complete API Reference

ai.models.* — Generative AI

| Method | Default Model | Returns | |---|---|---| | generateText(params, pollOptions?) | gemini-3-flash-preview | GenerationResult<string> | | generateImage(params, pollOptions?) | gemini-3.1-flash-image-preview | GenerationResult<string> (URL) | | generateVideo(params, pollOptions?) | veo-3.1-fast-generate-001 | GenerationResult<string> (URL) | | generateAudio(params, pollOptions?) | eleven_v3 | GenerationResult<string> (URL) | | upscale(params, pollOptions?) | — | GenerationResult<string> (URL) |

ai.jobs.* — Job Tracking

| Method | Description | |---|---| | getStatus(id) | Get status and result of a job | | list(params?) | List jobs with filters and pagination | | listAll(params?) | Auto-paginating async iterator | | getStats(startDate, endDate) | Credit usage statistics |

ai.media.* — Media Management

| Method | Description | |---|---| | upload(params) | Upload a file (max 20 MB) | | list(params?) | List files with metadata filter | | get(id) | Get file details | | delete(id) | Delete a file | | updateMetadata(id, metadata) | Update file metadata |

ai.voices.* — TTS Voices

| Method | Description | |---|---| | list(params?) | List voices with language filter |


🏗️ Framework Integration Examples

Next.js (App Router)

// app/api/generate/route.ts
import { VidtoryAI } from '@vidtory/ai-sdk';
import { NextResponse } from 'next/server';

const ai = new VidtoryAI(); // reads VIDTORY_API_KEY from env

export async function POST(request: Request) {
  const { prompt } = await request.json();

  const result = await ai.models.generateImage({ prompt });

  return NextResponse.json({ imageUrl: result.result });
}

Express.js

import express from 'express';
import { VidtoryAI, VidtoryAPIError } from '@vidtory/ai-sdk';

const app = express();
const ai = new VidtoryAI();

app.post('/generate', async (req, res) => {
  try {
    const result = await ai.models.generateText({
      prompt: req.body.prompt,
    });
    res.json({ text: result.result });
  } catch (error) {
    if (error instanceof VidtoryAPIError) {
      res.status(error.statusCode || 500).json({ error: error.message });
    } else {
      res.status(500).json({ error: 'Internal Server Error' });
    }
  }
});

Convex (HTTP Action)

import { httpAction } from './_generated/server';
import { VidtoryAI } from '@vidtory/ai-sdk';

export const generate = httpAction(async (ctx, request) => {
  const ai = new VidtoryAI({
    apiKey: process.env.VIDTORY_API_KEY,
  });

  const { prompt } = await request.json();
  const result = await ai.models.generateImage({ prompt });

  return new Response(JSON.stringify({ url: result.result }), {
    headers: { 'Content-Type': 'application/json' },
  });
});

📋 Environment Variables

| Variable | Description | |---|---| | VIDTORY_API_KEY | API key (fallback if not in constructor) | | VIDTORY_BASE_URL | Base URL (fallback, default: https://bapi.vidtory.net) | | VIDTORY_DEBUG | Set to 'true' to enable debug logging |


🔧 Build & Development

# Install dependencies
npm install

# Build (CJS + ESM + declarations)
npm run build

# Type-check only
npm run build:tsc

# Run tests
npm test

Build Output

dist/
├── index.js       # CommonJS
├── index.mjs      # ES Module
├── index.d.ts     # TypeScript declarations (CJS)
├── index.d.mts    # TypeScript declarations (ESM)
├── index.js.map   # Source map (CJS)
└── index.mjs.map  # Source map (ESM)

📄 License

MIT