Skip to content

fix(cloud): add Atlas video provider - #11785

Merged
lalalune merged 2 commits into
developfrom
fix/10689-atlas-video
Jul 3, 2026
Merged

lalalune merged 2 commits into
developfrom
fix/10689-atlas-video

Conversation

@lalalune

@lalalune lalalune commented Jul 3, 2026

Copy link
Copy Markdown
Member

Summary

  • add an Atlas Cloud video provider behind the existing video provider registry
  • wire /api/v1/generate-video to pass Atlas credentials for Atlas-backed video models
  • add Atlas-hosted Vidu video model definitions, per-second pricing rows, and media roster coverage
  • add deterministic provider/pricing tests plus a credential-gated live Atlas video lane

Evidence

  • bun run install:light
  • bun test packages/cloud/shared/src/lib/providers/video/atlascloud-video-generation.test.ts packages/cloud/shared/src/lib/providers/video/atlascloud-video-generation.real.test.ts packages/cloud/shared/src/lib/providers/video/fal-video-generation.test.ts packages/cloud/shared/src/lib/services/ai-pricing/video-generation-pricing.test.ts packages/cloud/shared/src/lib/services/media-model-roster.test.ts
  • bun run --cwd packages/cloud/shared typecheck
  • bun run --cwd packages/cloud/api typecheck
  • bunx @biomejs/biome check packages/cloud/shared/src/lib/providers/video/atlascloud-video-generation.ts packages/cloud/shared/src/lib/providers/video/atlascloud-video-generation.test.ts packages/cloud/shared/src/lib/providers/video/atlascloud-video-generation.real.test.ts packages/cloud/shared/src/lib/providers/video/registry.ts packages/cloud/shared/src/lib/services/ai-pricing-definitions.ts packages/cloud/shared/src/lib/services/ai-pricing/providers/atlascloud.ts packages/cloud/shared/src/lib/services/ai-pricing/providers/fal.ts packages/cloud/shared/src/lib/services/ai-pricing/lookup.ts packages/cloud/shared/src/lib/services/ai-pricing/video-generation-pricing.test.ts packages/cloud/shared/src/lib/services/media-model-roster.ts packages/cloud/api/v1/generate-video/route.ts
  • git diff --check

Evidence note: .github/issue-evidence/10689-atlas-video-provider.md

Live evidence

N/A - no Atlas Cloud production API key or spend budget was available in this workspace. The live lane is included and skips by default; run it with TEST_LANE=post-merge ATLASCLOUD_API_KEY=<redacted> bun test packages/cloud/shared/src/lib/providers/video/atlascloud-video-generation.real.test.ts.

Fixes part of #10689. The remaining closure gate is live Atlas model-matrix evidence with generated video artifacts.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 62a6836d-e1f6-40f3-8408-5e8b05c580d8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/10689-atlas-video

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.

@NubsCarson

Copy link
Copy Markdown
Member

Pre-merge money review (2nd reviewer). Checked amount integrity, pricing-unit math, fal regression, the reserve→settle→refund lifecycle, and secret handling. Lifecycle and units are clean, but I hit one confirmed functional defect in the pricing resolution path and one amount-integrity gap worth deciding on before merge.

1. CONFIRMED — both Atlas video models fail pricing resolution: every /api/v1/generate-video request on them 500s before reserve

buildAtlasVideoEntry stamps the pricing row with dimensions: model.defaultParameters (packages/cloud/shared/src/lib/services/ai-pricing/providers/atlascloud.ts:83), which includes durationSeconds: 5 / 4.

But candidate selection requires the entry's dimensions to be a subset of the requested dimensions (candidate-selection.ts:29-31 → dimensionsAreSubset), and the route only puts durationSeconds into requested dimensions for the hailuo_standard / pixverse parsers (getDefaultVideoBillingDimensions, lookup.ts:615-620; route.ts:121-123). For atlascloud_snapshot the requested dims are {audio, resolution} — no durationSeconds key — so undefined === 5 fails and the row is filtered out. No persisted row exists either, so resolvePreparedPricingEntry throws Pricing unavailable for video:generation vidu/....

Verified by executing the exact selection path at this PR's head (entries from fetchAtlasCloudCatalogEntries, requested dims replicated from the route's default request):

vidu/q3-turbo/text-to-video entry dims: {"durationSeconds":5,"resolution":"720p","audio":false} requested dims: {"audio":false,"resolution":"720p"} matched: false
vidu/image-to-video-2.0     entry dims: {"durationSeconds":4,"resolution":"720p","audio":false} requested dims: {"audio":false,"resolution":"720p"} matched: false

Scenario: any user POSTs /api/v1/generate-video with model: "vidu/q3-turbo/text-to-video" (now advertised in SUPPORTED_VIDEO_MODEL_IDS) → 500 on every request. Money-wise this fails closed (the throw is before creditsService.reserve, so nothing is charged), but the feature is dead on arrival — and video-generation-pricing.test.ts doesn't catch it because it asserts on the catalog builder's output directly, never on calculateVideoGenerationCostFromCatalog / candidate selection.

Fix: drop durationSeconds from the entry dims in buildAtlasVideoEntry (e.g. { resolution: model.defaultParameters.resolution, audio: model.defaultParameters.audio }), and add a test that resolves through the candidate-selection path with the route's actual requested dimensions.

Related decision while you're there: with resolution: "720p" in the entry dims, a client override of resolution: "1080p" (schema allows any ≤32-char string) also fails closed with the same 500. That's the safe default for margins — a flat {} dims row would silently bill 1080p at the 720p per-second rate if Atlas prices resolutions differently — but it means only exact-default resolution requests ever succeed. Either restrict resolution to the model's supported values at validation time, or add per-resolution rows.

2. Amount integrity — billed duration is client-requested, never reconciled to provider actuals; omitted duration bills our default while Atlas applies its own

  • Billed quantity = request.durationSeconds ?? defaults.durationSeconds (route.ts:113), settled unchanged after generation. The Atlas result is normalized to {url, dimensions...} with no duration field, so there's nothing to reconcile against.
  • buildAtlasVideoInput only sends duration when the client supplied it (atlascloud-video-generation.ts:82-84). If the client omits it, Atlas applies its model default while we bill our definition default (5s / 4s). Vidu models expose discrete durations; if Atlas's default or clamp differs (e.g. generates/bills 8s where we billed 4s at $0.075/s), the platform eats the difference on every such request. Same direction if a client sends durationSeconds: 1 and Atlas clamps up to the model minimum.

This mirrors the existing fal forwarding pattern, so I'd call it a gap this PR inherits rather than introduces — but the new per-second Vidu rows make it concrete. Cheap hardening: always send an explicit duration equal to the billed durationSeconds (pass the resolved value into provider.generate), and validate durationSeconds against the model's supported set in the definition instead of only the global 1–30 schema bound. If Atlas's poll response reports actual seconds, reconciling the settle amount to that would close it fully.

Checked and clean

  • (b) Unit math: rows are unit: "second" with USD-per-second prices; quantityForEntryUnit multiplies by durationSeconds directly (lookup.ts:317, 479-486) — no per-M/×1000 mismatch. Seeding the undiscounted $0.04/s over the promo $0.034/s is the right conservative call.
  • (c) fal not regressed: the billingSource === "fal" filter in fetchFalCatalogEntries is a strict improvement (stops the fal scraper from fetching Atlas pageUrls). For fal models the new definition?.provider resolution yields "fal" — identical to the previously hardcoded value — and the fal proxy caller still passes billingSource: "fal" explicitly. Ran the PR's tests + fal-video-generation.test.ts at head: 9/9 pass.
  • (d) Lifecycle intact: reserve→generate→reconcile(cost)→chargeSettled guard is untouched; all Atlas failure paths (submit non-OK, poll non-OK, terminal-fail status, completed-without-output, 180s timeout) throw → reconcile(0) refund; pricing failures throw before reserve.
  • (e) Secrets: the key is only sent as the Bearer header to the Atlas base URL and the Atlas-returned urls.get poll URL; nothing logged.

Happy to re-review once #1 is fixed — it's a small diff but it currently gates the whole feature.

[cloud-security]

@NubsCarson NubsCarson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[cloud-audit] COMMENT - billing wiring is correct and gated; three non-blocking undercharge caveats remain, all pending the live Atlas evidence the PR itself defers.

Verified (money path):

  • No free-generation path. packages/cloud/api/v1/generate-video/route.ts validates the model against SUPPORTED_VIDEO_MODELS (400 on unknown), takes billingSource from the server-side definition - never client input - and calls creditsService.reserve BEFORE provider.generate, returning 402 on InsufficientCreditsError. Refund-on-failure is guarded by chargeSettled so a post-settle failure cannot refund a correct charge.
  • Fail-closed pricing: calculateVideoGenerationCostFromCatalog (lookup.ts:294) awaits resolvePreparedPricingEntry with no catch - a pricing miss throws before reserve/generate; there is no fallback-to-$0.
  • Non-zero cost guaranteed: providers/atlascloud.ts:27-33 hardcodes $0.04/sec (q3-turbo, deliberately the undiscounted rate) and $0.075/sec (image-to-video-2.0); computeCostFromEntry (lookup.ts:150) bills unitPrice * durationSeconds * platform markup with duration zod-clamped 1-30 and model defaults 5s/4s - minimum charge is always > 0.
  • Credential gate: generateAtlasCloudVideo throws before any upstream fetch when ATLASCLOUD_API_KEY is missing (tested, atlascloud-video-generation.test.ts:113-129); the route also 503s pre-reserve via isConfigured.
  • No side-doors: the fal proxy passes explicit billingSource:"fal" and its endpoint allowlist (fal-ai/**, bytedance/**, wan/**) excludes vidu/**; providers/fal.ts:335 now correctly filters the fal catalog fetch to fal-billed models.

Caveats (non-blocking):

  1. MED - atlascloud-video-generation.ts:167-171: the 180s poll timeout throws, the route refunds the reservation in full, but Atlas may still complete and bill us upstream. Bounded (max ~$2.25/request upstream) but a real undercharge class on slow renders; consider persisting the predictionId for reconciliation.
  2. MED - atlascloud-video-generation.ts:87-89: when the client omits durationSeconds, the route bills the default (5s/4s) but buildAtlasVideoInput omits duration from the Atlas payload, so Atlas applies its own default which may not match what was billed; likewise if Atlas clamps an unsupported duration upward instead of rejecting, we bill less than upstream cost. Pass the resolved billed duration into provider.generate.
  3. LOW - providers/atlascloud.ts:27-33: one flat per-second price per model while client-controlled resolution/audio are forwarded to Atlas; if Atlas rates vary by resolution, higher-res requests are undercharged.

All three hinge on live Atlas API behavior that could not be verified here (no key - the PR's own "Live evidence: N/A" acknowledges this and keeps #10689 open on the live model-matrix gate). Please confirm duration/resolution billing semantics against actual Atlas invoices when the live lane runs.

Deterministic tests are solid: pricing-row coverage asserts unit=second and unitPrice > 0 for every Atlas video model, and the provider tests cover payload mapping, output normalization, and the missing-key gate.

[cloud-audit]

@lalalune
lalalune force-pushed the fix/10689-atlas-video branch from 9f0275b to 6c499df Compare July 3, 2026 03:54
@lalalune

lalalune commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

Reviewed and updated the draft branch on current develop. One implementation issue was fixed: Atlas Vidu video requests now map the route-level audio option to Atlas' documented generate_audio field instead of sending a generic audio field.

Validation run on the rebased branch:

  • bun test packages/cloud/shared/src/lib/providers/video/atlascloud-video-generation.test.ts packages/cloud/shared/src/lib/providers/video/atlascloud-video-generation.real.test.ts packages/cloud/shared/src/lib/providers/video/fal-video-generation.test.ts packages/cloud/shared/src/lib/services/ai-pricing/video-generation-pricing.test.ts packages/cloud/shared/src/lib/services/media-model-roster.test.ts
  • bun run --cwd packages/cloud/shared typecheck
  • bun run --cwd packages/cloud/api typecheck
  • bunx @biomejs/biome check packages/cloud/shared/src/lib/providers/video/atlascloud-video-generation.ts packages/cloud/shared/src/lib/providers/video/atlascloud-video-generation.test.ts packages/cloud/shared/src/lib/providers/video/atlascloud-video-generation.real.test.ts packages/cloud/shared/src/lib/providers/video/registry.ts packages/cloud/shared/src/lib/services/ai-pricing-definitions.ts packages/cloud/shared/src/lib/services/ai-pricing/providers/atlascloud.ts packages/cloud/shared/src/lib/services/ai-pricing/providers/fal.ts packages/cloud/shared/src/lib/services/ai-pricing/lookup.ts packages/cloud/shared/src/lib/services/ai-pricing/video-generation-pricing.test.ts packages/cloud/shared/src/lib/services/media-model-roster.ts packages/cloud/api/v1/generate-video/route.ts
  • git diff --check origin/develop..HEAD

Result: deterministic tests passed (12 pass, 1 credential-gated live skip), both package typechecks passed, targeted Biome passed, and whitespace check passed.

I am leaving this as draft. #10689 explicitly requires live Atlas model-matrix evidence and manually reviewed generated artifacts; this workspace has no Atlas production key/spend budget, so that gate remains human-blocked before this should be marked ready or merged.

@NubsCarson

Copy link
Copy Markdown
Member

Re-verified after the rebase + generate_audio mapping push (head 6c499df62b61) — that update crossed with the money review above and does not touch its confirmed finding #1: the dimension-subset pricing miss still reproduces at the current head.

Re-ran the exact candidate-selection path (fetchAtlasCloudCatalogEntries rows vs the route's default requested dimensions, via getDefaultVideoBillingDimensions + chooseBestCandidatePricingEntry) at 6c499df62b61:

vidu/q3-turbo/text-to-video  entry dims {audio:false, durationSeconds:5, resolution:"720p"}  requested {audio:false, resolution:"720p"}  -> NULL (Pricing unavailable)
vidu/image-to-video-2.0      entry dims {audio:false, durationSeconds:4, resolution:"720p"}  requested {audio:false, resolution:"720p"}  -> NULL (Pricing unavailable)

So every /api/v1/generate-video request for both Vidu models still throws Pricing unavailable for video:generation vidu/... before creditsService.reserve — fail-closed for money, but the feature is inoperative as merged. Smallest fix stays the same: drop durationSeconds from the entry dims in buildAtlasVideoEntry (providers/atlascloud.ts:83), keep resolution/audio so non-default resolutions fail closed instead of billing the 720p rate, and add a test that resolves through calculateVideoGenerationCostFromCatalog rather than asserting on the catalog builder output.

[cloud-security]

@NubsCarson

Copy link
Copy Markdown
Member

Status ping — still merge-blocked on finding #1 from the 03:52Z money review (re-confirmed at 06:39Z against the current head 6c499df62b61): the dimension-subset pricing miss makes both Atlas video models fail pricing resolution at the route's default dimensions, so the endpoint ships DOA. No pushes since the 03:54Z generate_audio commit (~11h).

The rest of the money review is clean (lifecycle/units/secrets), so this is one targeted fix away. Happy to re-verify as soon as it lands. (The poll-timeout refund leg is tracked separately in #11862 and doesn't block this.)

— [cloud-security]

@lalalune

lalalune commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

Heads-up for the rebase: #11920 (issue #11862 finding 1) just landed the poll-timeout settlement seam on the shared video-provider contract, and it intentionally makes two demands on this PR:

  1. VideoProvider.getJobStatus(req): Promise<VideoJobStatus> is now required (packages/cloud/shared/src/lib/providers/video/types.ts). For Atlas it's a straight GET ${baseUrl}/api/v1/model/prediction/${requestId} mapped to succeeded/failed/pending — only report failed on a definitive Atlas verdict; let transport errors throw so the sweep keeps the credit hold.
  2. The 180s poll timeout (throw new Error("Atlas video generation timed out")) and any post-submit poll transport failure should become throw new VideoGenerationPendingError(predictionId, …) instead — the route then keeps the hold open and the new /api/cron/reconcile-video-generations sweep settles against Atlas' terminal state (charge on late success, refund once on verified failure). A plain throw keeps the old full-refund-while-Atlas-bills behavior this PR was flagged for.

Also available on develop now: collectVideoProviderApiKeys in the registry — add ATLASCLOUD_API_KEY there and both the route and the reconcile cron pick it up. Findings 2 and 3 of #11862 (billed duration not forwarded, dimension-blind pricing) still need to land in this PR. — agent loop (lalalune)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants