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

@picsart/ai-sdk

v5.35.1

Published

Type-safe SDK for 100+ AI models — image, video, audio, and text generation with Picsart

Readme

@picsart/ai-sdk

Generate images, video, audio, and text with 100+ AI models.

Documentation

Full guides, the model catalog, and the API reference live on the Picsart API Platform:

Repository: github.com/PicsArt/ai-sdk

Quick Start

npm install @picsart/ai-sdk
import { createClient, Models, Model, catalog } from '@picsart/ai-sdk'

// Create a client — pass your Picsart API key
const ai = createClient({
  apiKey: process.env.PICSART_API_KEY,
  apiUrl: 'https://api.picsart.com',
})

// Generate (Models.* are typed model-id constants)
const result = await ai.generate(Models.Flux2Pro, { prompt: 'a cat on mars' })
console.log(result.url)

// Browse models — the `catalog` accessor
catalog.all()                              // every model
catalog.find({ output: 'video' })          // video models only
catalog.search('kling')                    // search by name/id/provider

// Model metadata & params — the `Model` accessor
Model(Models.Flux2Pro).name                // 'Flux 2 Pro'
Model(Models.Flux2Pro).meta()              // mode, provider, badges, …
Model(Models.Flux2Pro).params()            // accepted parameters
Model(Models.Flux2Pro).params().toSchema() // param schema

// Validate input (never throws) → { valid, errors? }
Model(Models.Flux2Pro).validate({ prompt: 'a cat' })

Authentication

Pass apiKey and the SDK sends Authorization: Bearer <apiKey> on every request (a leading Bearer is stripped if present). Create a key from the Authentication guide.

const ai = createClient({
  apiKey: process.env.PICSART_API_KEY,
  apiUrl: 'https://api.picsart.com',
})

Keep the key server-side — anything shipped to a browser is public.

Apps that already handle auth themselves (session cookies, token refresh, a backend proxy) can pass their own fetch instead. It takes precedence over apiKey when both are set:

const ai = createClient({
  fetch: myAuthenticatedFetch,
  apiUrl: 'https://api.picsart.com',
})

createClient throws if neither apiKey nor fetch is provided.

Drive Integration

Auto-save generations to Picsart Drive:

const ai = createClient({
  apiKey: process.env.PICSART_API_KEY,
  apiUrl: 'https://api.picsart.com',
  drive: { folder: 'AI Playground' },
})

// Generates and auto-saves to the root folder
const result = await ai.generate(Models.Flux2Pro, { prompt: 'a cat' })
// result.drive = { uid: '...', folder: { name: 'AI Playground', uid: '...' } }

// Save to a subfolder
const board = await ai.drive.ensureFolder('Cats')
const result = await ai.generate(Models.Flux2Pro, { prompt: 'a cat' }, { folder: board })

// Browse Drive
const folders = await ai.drive.folders()
const items = await ai.drive.list()

Text Generation (LLMs)

Claude, GPT, and Gemini text models are called with generateText(). Single-shot: pass a prompt and optional image(s)/video, get text back. They surface in the catalog as mode: 'text'. Each vendor uses its own native workflow, so capabilities aren't lost — Gemini accepts video input, Claude uses the Anthropic-native messages API.

import { createClient, Models } from '@picsart/ai-sdk'

const ai = createClient({ apiKey: process.env.PICSART_API_KEY, apiUrl: 'https://api.picsart.com' })

// Text in, text out
const { text } = await ai.generateText(Models.ClaudeOpus48, { prompt: 'Explain RAG in one line.' })
console.log(text)

// Optional vision input + reasoning level (OpenAI / Gemini)
const res = await ai.generateText(Models.Gpt55, {
  prompt: 'What is in this image?',
  imageUrls: ['https://cdn.example.com/photo.jpg'],
  thinking: 'high',            // 'off' | 'low' | 'medium' | 'high'
})
console.log(res.text)
console.log(res.raw)            // full backend response — usage, finish_reason, etc.

// Gemini accepts video input
await ai.generateText(Models.Gemini3Pro, {
  prompt: 'Summarize this clip',
  videoUrl: 'https://cdn.example.com/clip.mp4',
})

// Browse text models
catalog.find({ output: 'text' })

Thinking level maps per vendor: OpenAI → reasoning_effort, Gemini → thinkingConfig.thinkingLevel (LOW/HIGH). Claude’s claude/v1/messages workflow exposes no thinking knob, so Claude models omit thinking.

generateText() is type-narrowed to text models (TextModelId); calling it with an image/video model throws, and generate() throws on a text model — use the matching method for each.

Voice, Avatar & Template Catalogs

Models with catalog-backed params (voices, avatars, effect / caption templates) serve their option lists from platform catalog tasks (<vendor>/v1/catalog/<voices|avatars|templates|…>) — nothing is bundled; the workers cache the lists and answer fast. Fetch them via ai.catalogs:

// One page at a time — load more on scroll/pagination via nextCursor
const page = await ai.catalogs.voices('heygen-video-avatar')
// page: { items: CatalogItem[], nextCursor: string | null }
const more = await ai.catalogs.voices('heygen-video-avatar', { cursor: page.nextCursor! })

// Optional: preload the first page of every bound catalog at client creation
const ai = createClient({ apiKey, catalogs: { preload: true } })

// Fetched pages accumulate into the model's options, so existing accessors
// (and every picker built on them) see everything loaded so far:
Model('heygen-video-avatar').params().catalog('videoId')?.catalogOptions

CatalogItem is the standard shape across all vendors: { id, name, description?, tags, preview? { imageUrl | videoUrl | audioUrl }, meta? }id is sent back verbatim on generate as the bound param's value. Validation never requires hydration: catalog-bound params accept any id and the platform validates for real.

Advanced Lifecycle

For progress tracking, cancellation, and job recovery:

// Submit without waiting
const handle = await ai.submit(Models.KlingV3Pro, { prompt: 'a sunset' })

// Subscribe to status updates
for await (const update of ai.subscribe(handle)) {
  console.log(update.status, update.progress?.percent)
}

// Or poll manually
const status = await ai.status(handle)

Error Handling

Every failure thrown by generate(), generateText(), submit(), and result() is an ApiError with the same four fields, so you can branch on the error instead of pattern-matching its message:

import { createClient, Models, ApiError } from '@picsart/ai-sdk'

try {
  const result = await ai.generate(Models.Flux2Pro, { prompt: 'a cat on mars' })
} catch (err) {
  if (err instanceof ApiError) {
    err.status   // 402            — HTTP status, or its synthesized equivalent
    err.code     // 'payment_required' — platform `reason`, else an SDK code
    err.reason   // same value as `code`, named after the platform's own field
    err.message  // 'Submit failed (402): Not enough credits'

    if (err.status === 402) return topUpCredits()
    if (err.status === 429 || err.status >= 500) return retry()
    if (err.code === 'validation_error') return showFormError(err.message)
  }
  throw err
}

code carries the platform's reason verbatim whenever the API supplies one (content_moderation, unauthorized, …). When it doesn't, the SDK fills in a conventional slug for the status — payment_required for 402, rate_limited for 429, and so on.

Failures that never reach the network get the status they semantically deserve, so one retry predicate covers every case:

| Failure | status | code | |---------|----------|--------| | Unknown model id | 400 | unknown_model | | generate() on a text model (or the reverse) | 400 | wrong_model_mode | | Parameter validation | 400 | validation_error | | Async lifecycle on an execute-only transport | 400 | unsupported_transport | | HTTP error from the API | the response's status | platform reason, else the status slug | | Poll deadline exceeded | 408 | timeout | | Aborted via options.signal, or a canceled job | 499 | aborted / canceled | | Job finished FAILED | the task's statusCode, else 502 | platform reason, else generation_failed | | Response the SDK can't parse | 502 | invalid_response |

Aborts raised by fetch itself are deliberately not wrapped, so err.name === 'AbortError' keeps working on the DOMException.

message is human-readable and may change between versions — branch on status and code, not on the message text.

Public API

The SDK exports 8 symbols:

| Export | Type | Description | |--------|------|-------------| | createClient | function | Create an AI client from an API key (or a custom authenticated fetch) | | Models | object | Model catalog: 108 models + list/search/validate/toSchema | | GenerateResult | type | { url, model, handle, drive? } | | ClientConfig | type | { apiKey?, fetch?, apiUrl, drive? } — one of apiKey / fetch required | | AuthenticatedFetch | type | (url, init?) => Promise<Response> — for the custom-fetch path | | SdkTransport | type | Advanced: custom transport interface | | WorkflowJobHandle | type | Job handle for submit/status/cancel | | ApiError | class | Unified error: { status, code, reason, message } — see Error Handling |

Package Structure

packages/ai-sdk/
  package.json
  tsconfig.json
  tsup.config.ts
  src/
    index.ts                    # Public API entry (7 exports)
    client/
      types.ts                  # ClientConfig, GenerateResult, DriveConfig
      transport.ts              # Authenticated fetch → SdkTransport
      prepare.ts                # Validate input, build payload, parse result
      drive.ts                  # Drive folder management + file saving
      index.ts                  # createClient() factory
    core/
      types.ts                  # ModelDefinition, ParamConfig, GenerationContext
      workflow.ts               # Generic polling/execution engine
      contracts.ts              # Runtime input validation
      schema.ts                 # ParamConfig → JSON Schema
      response.ts               # Vendor-agnostic result extraction
      pricing.ts                # ToolId resolution
      model-registry.ts         # Model lookup indexes
      providers.ts              # Provider colors, labels, names
      voices.ts                 # Voice catalogs (ElevenLabs, OpenAI, Gemini)
      helpers.ts                # Vendor utilities
    generated/
      model-constants.ts        # AUTO-GENERATED: Models object + 108 constants
      model-input-types.ts      # AUTO-GENERATED: per-model TypeScript input types
    vendors/
      define.ts                 # defineModels() framework + params.* helpers
      presets.ts                # Reusable paramConfig factories
      catalog/
        index.ts                # Aggregation: ALL_MODELS, VENDOR_CATALOGS
        kling.ts                # One file per vendor (31 total)
        flux.ts
        ...
  __tests__/                    # SDK tests
  scripts/                      # Build scripts

Adding a New Model

Standard flow (pass-through payload)

When the backend accepts param values as-is (no field renaming needed):

  1. Add config in src/vendors/catalog/{vendor}.ts via defineModels():
    • buildPayload is optional — omit it and param values pass through as-is
  2. Run npm run build:model-constants to regenerate constants
  3. Run npm run build:model-input-types to regenerate TypeScript types
  4. Model automatically appears in catalog.all() and as the Models.NewModel id constant

With payload transforms

When the vendor API uses different field names or value formats:

  1. Define the model config as above (no buildPayload)
  2. Run npm run build:model-input-types — generates typed input for your model
  3. Create src/vendors/catalog/{vendor}.payloads.ts:
    import type { ModelInput } from '../../generated/model-input-types.ts';
    import { registerPayloads } from '../define.ts';
    import { SPECS, MODELS } from './{vendor}.ts';
    
    registerPayloads({ SPECS, MODELS }, {
      'model-id': (input: ModelInput<'model-id'>) => ({
        prompt: input.prompt,
        aspect_ratio: input.aspectRatio,  // rename for vendor API
      }),
    });
  4. Import the .payloads.ts file in src/vendors/catalog/index.ts (after the vendor import)

See src/vendors/catalog/luma.ts + src/vendors/catalog/luma.payloads.ts for a working example.