Skip to content

feat: add Seedance 2 video generation via PiAPI provider#186

Merged
SecurityQQ merged 2 commits intomainfrom
feature/seedance-2-piapi
Mar 29, 2026
Merged

feat: add Seedance 2 video generation via PiAPI provider#186
SecurityQQ merged 2 commits intomainfrom
feature/seedance-2-piapi

Conversation

@SecurityQQ
Copy link
Copy Markdown
Contributor

@SecurityQQ SecurityQQ commented Mar 29, 2026

Summary

Add PiAPI as a new provider for Seedance 2 video generation (ByteDance) across all three SDK integration layers.

Integration Layers

1. Classic Provider (src/providers/piapi.ts)

  • Extends BaseProvider with submit, getStatus, getResult
  • Named convenience methods (same pattern as fal):
    • textToVideo() — text prompt to video
    • imageToVideo() — image(s) + prompt to video
    • editVideo() — edit existing video based on prompt
    • extendVideo() — extend a previously generated video
    • removeWatermark() — remove watermark from video
  • Auto watermark removal for seedance-2-preview

2. Model Definitions (src/definitions/models/seedance.ts)

  • seedance-2-preview — high quality, auto watermark removal
  • seedance-2-fast-preview — fast, no watermark removal
  • Zod schema with all Seedance-specific fields (duration 5/10/15, aspect ratios 16:9/9:16/4:3/3:4, image_urls, video_urls, parent_task_id)

3. AI SDK v3 Provider (src/ai-sdk/providers/piapi.ts)

  • VideoModelV3 implementation
  • Auto-maps files[] to image_urls/video_urls by extension
  • parent_task_id via providerOptions.piapi
  • Auto watermark removal for seedance-2-preview

Supported Modes

Mode How
Text-to-video prompt only
Image-to-video prompt + image_urls (max 9, @imageN refs)
Video edit prompt + video_urls
Character replacement prompt + video_urls + image_urls
Extend video parent_task_id
Watermark removal Automatic for seedance-2-preview

Usage

// Via varg gateway (recommended — caching, billing, stable URLs)
import { varg } from "vargai/ai-sdk/providers/varg";

const result = await generateVideo({
  model: varg.videoModel("seedance-2-preview"),
  prompt: "A woman sings and strums her guitar",
  duration: 5,
});

// Direct to PiAPI (bypasses gateway)
import { piapi } from "vargai/ai-sdk/providers/piapi";

const result = await generateVideo({
  model: piapi.videoModel("seedance-2-preview"),
  prompt: "A woman sings and strums her guitar",
  duration: 5,
});

// Classic provider — named convenience methods
import { piapiProvider } from "vargai/providers";

// Text-to-video
const t2v = await piapiProvider.textToVideo({
  prompt: "A cat walks through a garden",
  duration: 10,
  aspectRatio: "16:9",
});

// Image-to-video
const i2v = await piapiProvider.imageToVideo({
  prompt: "The cat in @image1 walks through a garden",
  imageUrls: ["https://example.com/cat.jpg"],
  duration: 5,
});

// Video edit
const edit = await piapiProvider.editVideo({
  prompt: "Transform into cinematic warm golden lighting",
  videoUrl: "https://example.com/input.mp4",
});

// Extend video
const ext = await piapiProvider.extendVideo({
  parentTaskId: "previous-task-id",
  prompt: "Continue the scene...",
  duration: 5,
});

Companion PR

Add PiAPI as a new provider for Seedance 2 video generation (ByteDance).

Three integration layers:
- Classic provider (src/providers/piapi.ts) extending BaseProvider
- Model definitions (src/definitions/models/seedance.ts) for both variants
- AI SDK v3 provider (src/ai-sdk/providers/piapi.ts) with VideoModelV3

Models:
- seedance-2-preview: high quality, auto watermark removal
- seedance-2-fast-preview: fast, no watermark removal

Supports all Seedance modes: T2V, I2V, video edit, extend, watermark removal.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e62e0fb5-28a4-402d-ab7a-781af9e827c1

📥 Commits

Reviewing files that changed from the base of the PR and between 8c4aca2 and 6378d20.

📒 Files selected for processing (1)
  • src/providers/piapi.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/providers/piapi.ts

📝 Walkthrough

walkthrough

adds a piapi-backed seedance video provider, new seedance model defs, and provider registration; implements async task submission, polling, optional watermark removal, and video download across ai-sdk and providers layers.

changes

Cohort / File(s) Summary
ai-sdk provider
src/ai-sdk/providers/piapi.ts
new video provider implementing VideoModelV3: resolves api key, converts file inputs to image/video urls, submits tasks to piapi, polls for completion/timeout, optionally removes watermark, downloads video bytes, returns generated video + warnings.
seedance model definitions
src/definitions/models/seedance.ts, src/definitions/models/index.ts
adds seedance model schemas and two model exports (seedance-2-preview, seedance-2-fast-preview) with zod validation (prompt, image_urls, video_urls, duration, aspect_ratio, parent_task_id) and registers them in allModels.
provider implementation & registry
src/providers/piapi.ts, src/providers/index.ts
adds PiAPIProvider class and singleton (piapiProvider) with submit/getStatus/getResult, high-level helpers (textToVideo/imageToVideo/editVideo/extendVideo/removeWatermark), polling/timeouts, and registers provider in central index.

sequence diagrams

sequenceDiagram
    participant client as Client
    participant model as VideoModel
    participant provider as PiAPIProvider
    participant piapi as PiAPI
    participant seedance as Seedance

    client->>model: request generateVideo(prompt, options)
    model->>provider: submit task(payload)
    provider->>piapi: POST /api/v1/task {model: "seedance", input: ...}
    piapi-->>provider: {task_id, status: pending}

    loop poll
        provider->>piapi: GET /api/v1/task/{task_id}
        alt processing
            piapi-->>provider: {status: processing}
        else completed
            piapi-->>provider: {status: completed, output: {video: {url}}}
        else failed
            piapi-->>provider: {status: failed, error}
        end
    end

    alt preview model
        provider->>piapi: POST remove-watermark task
        piapi->>seedance: process removal
        seedance-->>piapi: cleaned video url
        piapi-->>provider: completed output
    end

    provider->>piapi: GET video bytes (download)
    piapi-->>provider: binary bytes
    provider-->>model: return video bytes + warnings
    model-->>client: deliver generated video
Loading

estimated code review effort

🎯 4 (complex) | ⏱️ ~60 minutes

poem

seedance hums, tasks in queue and play, meow
polls whisper back the progress day by day
watermarks melt, the clean clip flows
bytes arrive where the bright seed grows ✨📹

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed the title 'feat: add seedance 2 video generation via piapi provider' directly and concisely describes the main change—adding seedance 2 support through a new piapi provider implementation across all sdk layers.
Description check ✅ Passed the description is well-organized and clearly related to the changeset, covering all three integration layers (classic provider, model definitions, ai sdk v3), supported modes, and usage examples.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/seedance-2-piapi

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/ai-sdk/providers/piapi.ts (3)

310-310: consider validating modelId

unlike the together provider which validates against known models, this accepts any string and only fails at the api level. could add validation against known seedance models.

♻️ potential validation
+const SEEDANCE_MODELS = ["seedance-2-preview", "seedance-2-fast-preview"];
+
 export function createPiAPI(
   settings: PiAPIProviderSettings = {},
 ): PiAPIProvider {
   const { apiKey, baseUrl } = resolveConfig(settings);

   return {
     specificationVersion: "v3",
-    videoModel: (modelId) => new PiAPIVideoModel(modelId, baseUrl, apiKey),
+    videoModel: (modelId) => {
+      if (!SEEDANCE_MODELS.includes(modelId)) {
+        throw new NoSuchModelError({
+          modelId,
+          modelType: "videoModel",
+          message: `Unknown video model: ${modelId}. Available: ${SEEDANCE_MODELS.join(", ")}`,
+        });
+      }
+      return new PiAPIVideoModel(modelId, baseUrl, apiKey);
+    },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai-sdk/providers/piapi.ts` at line 310, The videoModel factory currently
constructs new PiAPIVideoModel(modelId, baseUrl, apiKey) without checking
modelId; add validation of modelId against the known Seedance model whitelist
before instantiating (e.g., inside the videoModel factory or within the
PiAPIVideoModel constructor). If modelId is not in the allowed list, return or
throw a clear validation error (rather than letting the API call fail) and
include the invalid modelId in the message; keep using baseUrl and apiKey when
creating the PiAPIVideoModel for valid IDs.

323-324: minor naming nit

piapi_provider uses snake_case but guidelines prefer camelcase. the export alias piapi is fine tho.

🧹 if you feel like it
-const piapi_provider = createPiAPI();
-export { piapi_provider as piapi };
+const piapiProvider = createPiAPI();
+export { piapiProvider as piapi };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai-sdk/providers/piapi.ts` around lines 323 - 324, Rename the snake_case
variable piapi_provider to camelCase piapiProvider where it's declared from
createPiAPI(), and update any internal references to use piapiProvider; keep the
export alias unchanged (export { piapiProvider as piapi }) so external imports
remain the same and ensure createPiAPI() usage and any other occurrences are
updated to the new identifier.

211-230: extension detection edge case

urls without extensions (like cdn urls with query params or signed urls) will default to image. might want to handle content-type detection or document this limitation.

// example edge case:
// https://cdn.example.com/video/abc123?signature=xyz
// -> no extension detected, treated as image

not blocking since the fallback is reasonable, just something to be aware of.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai-sdk/providers/piapi.ts` around lines 211 - 230, The current
extension-based media detection in the block handling options.files (the loop
that pushes into videoUrls or imageUrls and emits warnings for inline files)
misclassifies URLs that lack extensions (e.g., signed CDN URLs); update the
logic to perform a lightweight HEAD (or fetch with method HEAD) to inspect the
Content-Type when ext is missing or unrecognized and classify as video if
Content-Type starts with "video/", otherwise image; keep the existing extension
check first for performance, fall back to the HEAD request only when ext is
absent, and ensure failures/timeouts fall back to the current image default and
emit a warning via the same warnings array so callers are informed.
src/providers/piapi.ts (1)

200-243: consider reusing submit()

the fetch logic here duplicates what's in submit(). could potentially call submit("remove-watermark", { task_type: "remove-watermark", video_url: videoUrl }) and reuse that code path. not blocking tho, just a thought.

♻️ potential refactor
  async removeWatermark(videoUrl: string): Promise<string> {
    console.log("[piapi] submitting watermark removal...");

-    const response = await fetch(`${PIAPI_BASE_URL}/api/v1/task`, {
-      method: "POST",
-      headers: {
-        "X-API-Key": this.apiKey,
-        "Content-Type": "application/json",
-        Accept: "application/json",
-      },
-      body: JSON.stringify({
-        model: "seedance",
-        task_type: "remove-watermark",
-        input: { video_url: videoUrl },
-      }),
-    });
-
-    if (!response.ok) {
-      const errorText = await response.text();
-      throw new Error(
-        `piapi watermark removal submit failed (${response.status}): ${errorText}`,
-      );
-    }
-
-    const data = (await response.json()) as PiAPIResponse;
-    const taskId = data.data?.task_id;
-    if (!taskId) {
-      throw new Error("no task_id in piapi watermark removal response");
-    }
+    // note: would need to adjust submit() to accept raw input or add a new method
+    const taskId = await this.submitRaw({
+      model: "seedance",
+      task_type: "remove-watermark",
+      input: { video_url: videoUrl },
+    });

    console.log(`[piapi] watermark removal task: ${taskId}`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/providers/piapi.ts` around lines 200 - 243, The removeWatermark method
duplicates HTTP submit logic; refactor it to call the existing submit helper
instead of duplicating fetch code: replace the fetch/response parsing and
extraction of task_id inside removeWatermark by invoking
submit("remove-watermark", { video_url: videoUrl }) (or the appropriate submit
signature) to obtain the task id, then pass that id into this.waitForCompletion
and keep the existing result handling; update any call-sites in removeWatermark
that expect taskId or response shape accordingly and remove the duplicated fetch
block to centralize API request/response/error handling in submit().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/definitions/models/seedance.ts`:
- Line 11: Remove the unused import of aspectRatioSchema from the top of the
file: locate the import statement importing aspectRatioSchema and delete it,
keeping the local seedanceAspectRatioSchema that is actually used in this module
(check references to seedanceAspectRatioSchema, SeedanceModel, or related schema
definitions to confirm no other usage of aspectRatioSchema remains).

---

Nitpick comments:
In `@src/ai-sdk/providers/piapi.ts`:
- Line 310: The videoModel factory currently constructs new
PiAPIVideoModel(modelId, baseUrl, apiKey) without checking modelId; add
validation of modelId against the known Seedance model whitelist before
instantiating (e.g., inside the videoModel factory or within the PiAPIVideoModel
constructor). If modelId is not in the allowed list, return or throw a clear
validation error (rather than letting the API call fail) and include the invalid
modelId in the message; keep using baseUrl and apiKey when creating the
PiAPIVideoModel for valid IDs.
- Around line 323-324: Rename the snake_case variable piapi_provider to
camelCase piapiProvider where it's declared from createPiAPI(), and update any
internal references to use piapiProvider; keep the export alias unchanged
(export { piapiProvider as piapi }) so external imports remain the same and
ensure createPiAPI() usage and any other occurrences are updated to the new
identifier.
- Around line 211-230: The current extension-based media detection in the block
handling options.files (the loop that pushes into videoUrls or imageUrls and
emits warnings for inline files) misclassifies URLs that lack extensions (e.g.,
signed CDN URLs); update the logic to perform a lightweight HEAD (or fetch with
method HEAD) to inspect the Content-Type when ext is missing or unrecognized and
classify as video if Content-Type starts with "video/", otherwise image; keep
the existing extension check first for performance, fall back to the HEAD
request only when ext is absent, and ensure failures/timeouts fall back to the
current image default and emit a warning via the same warnings array so callers
are informed.

In `@src/providers/piapi.ts`:
- Around line 200-243: The removeWatermark method duplicates HTTP submit logic;
refactor it to call the existing submit helper instead of duplicating fetch
code: replace the fetch/response parsing and extraction of task_id inside
removeWatermark by invoking submit("remove-watermark", { video_url: videoUrl })
(or the appropriate submit signature) to obtain the task id, then pass that id
into this.waitForCompletion and keep the existing result handling; update any
call-sites in removeWatermark that expect taskId or response shape accordingly
and remove the duplicated fetch block to centralize API request/response/error
handling in submit().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f793065b-67a9-4d7d-b717-bacf6143b1e5

📥 Commits

Reviewing files that changed from the base of the PR and between 7b3d6c4 and 8c4aca2.

📒 Files selected for processing (5)
  • src/ai-sdk/providers/piapi.ts
  • src/definitions/models/index.ts
  • src/definitions/models/seedance.ts
  • src/providers/index.ts
  • src/providers/piapi.ts

*/

import { z } from "zod";
import { aspectRatioSchema } from "../../core/schema/shared";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# verify aspectRatioSchema is not used in this file
rg -n 'aspectRatioSchema' src/definitions/models/seedance.ts

Repository: vargHQ/sdk

Length of output: 118


remove unused import

aspectRatioSchema is imported on line 11 but never used in the file—you're using a local seedanceAspectRatioSchema instead. safe to delete. meow 🐱

fix
-import { aspectRatioSchema } from "../../core/schema/shared";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { aspectRatioSchema } from "../../core/schema/shared";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/definitions/models/seedance.ts` at line 11, Remove the unused import of
aspectRatioSchema from the top of the file: locate the import statement
importing aspectRatioSchema and delete it, keeping the local
seedanceAspectRatioSchema that is actually used in this module (check references
to seedanceAspectRatioSchema, SeedanceModel, or related schema definitions to
confirm no other usage of aspectRatioSchema remains).

Follow fal provider pattern: textToVideo, imageToVideo, editVideo,
extendVideo, removeWatermark — each with typed args instead of
leaking PiAPI's internal taskType concept.
@SecurityQQ SecurityQQ merged commit 14dd684 into main Mar 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant