Skip to content

Conversation

@tombeckenham
Copy link

@tombeckenham tombeckenham commented Jan 20, 2026

Summary

  • Add @tanstack/ai-fal package with image and video generation adapters
  • Leverage fal's type system (EndpointTypeMap) for autocomplete on 600+ models
  • Type-safe modelOptions that exclude fields TanStack AI handles (prompt, size, etc.)
  • Video adapter is experimental, supporting MiniMax, Luma, Kling, Hunyuan, etc.

Test plan

  • pnpm test:types passes
  • pnpm test:lib passes (27 tests)
  • pnpm build succeeds

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Add ai-fal package with fal.ai image & video adapters, size-mapping utilities, model typings, and API key/configuration helpers.
  • Tests

    • Add comprehensive tests for image/video adapters, size mapping, and client configuration.
  • Chores

    • Initialize package build/test/config and update ignore rules; remove a local AI settings file.

✏️ Tip: You can customize this high-level summary in your review settings.

Add @tanstack/ai-fal package with:

- Image adapter supporting 600+ fal.ai models with full type inference
- Video adapter (experimental) for MiniMax, Luma, Kling, Hunyuan, etc.
- Type-safe modelOptions using fal's EndpointTypeMap for autocomplete
- FalModel, FalModelInput, FalModelOutput utility types
- FalImageProviderOptions/FalVideoProviderOptions that exclude fields
  TanStack AI handles (prompt, size, etc.)
- Size preset mapping utilities for fal.ai format
- Comprehensive test coverage for both adapters

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@tombeckenham tombeckenham requested a review from a team January 20, 2026 09:34
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 20, 2026

📝 Walkthrough

Walkthrough

Adds a new TypeScript package @tanstack/ai-fal providing fal.ai image and video adapters, model typings, utilities, tests, and build config. Also deletes .claude/settings.local.json and updates .gitignore to ignore that file and re-add .output.

Changes

Cohort / File(s) Summary
Repo config
\.claude/settings.local.json`, `.gitignore``
Deleted .claude/settings.local.json. Updated .gitignore to re-add .output and ignore .claude/settings.local.json.
Package manifest & build
\packages/typescript/ai-fal/package.json`, `packages/typescript/ai-fal/tsconfig.json`, `packages/typescript/ai-fal/vite.config.ts``
New package manifest, tsconfig, and Vite/Vitest config exposing package entrypoints, scripts, deps, and coverage/test settings.
Adapters — Image
\packages/typescript/ai-fal/src/adapters/image.ts`, `packages/typescript/ai-fal/src/image/image-provider-options.ts``
New FalImageAdapter with size mapping, request assembly, response normalization (URL & base64), deterministic ID generation, and factories createFalImage / falImage. Size utility mapSizeToFalFormat added.
Adapters — Video
\packages/typescript/ai-fal/src/adapters/video.ts` `
New FalVideoAdapter supporting job submission, status polling/mapping, result retrieval (URL), aspect-ratio handling, ID generation, and factories createFalVideo / falVideo.
Model metadata
\packages/typescript/ai-fal/src/model-meta.ts` `
New model typing surface: FalModel, FalModelInput, FalModelOutput, FalImageProviderOptions, FalVideoProviderOptions, and re-export of EndpointTypeMap.
Utilities
\packages/typescript/ai-fal/src/utils/client.ts`, `packages/typescript/ai-fal/src/utils/index.ts``
New FalClientConfig, getFalApiKeyFromEnv(), configureFalClient(), generateId() and a utils barrel.
Public barrel
\packages/typescript/ai-fal/src/index.ts``
Package-level re-exports for adapters, factories, size utils, model types, and client utilities.
Tests
\packages/typescript/ai-fal/tests/image-adapter.test.ts`, `packages/typescript/ai-fal/tests/video-adapter.test.ts``
New tests mocking @fal-ai/client validating image/video flows, size mapping, base64 handling, options propagation, status mapping, and URL extraction.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant App as Application
    participant Adapter as FalImageAdapter
    participant FAL as `@fal-ai/client`
    participant API as fal.ai API

    User->>App: request image generation
    App->>Adapter: generateImages(options)
    Adapter->>Adapter: mapSizeToFalFormat, build input
    Adapter->>FAL: subscribe(model, input)
    FAL->>API: POST /image/generate
    API-->>FAL: { data: { images | image }, requestId }
    FAL-->>Adapter: response
    Adapter->>Adapter: transformResponse -> ImageGenerationResult
    Adapter-->>App: ImageGenerationResult
Loading
sequenceDiagram
    actor User
    participant App as Application
    participant Adapter as FalVideoAdapter
    participant FAL as `@fal-ai/client` (queue)
    participant API as fal.ai API

    User->>App: start video job
    App->>Adapter: createVideoJob(options)
    Adapter->>Adapter: build job input (prompt, duration, aspect_ratio)
    Adapter->>FAL: queue.submit(model, input)
    FAL->>API: POST /queue/submit
    API-->>FAL: { requestId: jobId }
    FAL-->>Adapter: jobId
    Adapter-->>App: VideoJobResult

    loop polling
      App->>Adapter: getVideoStatus(jobId)
      Adapter->>FAL: queue.status(jobId)
      FAL->>API: GET /queue/status
      API-->>FAL: { status, queue_position?, logs? }
      FAL-->>Adapter: status response
      Adapter->>Adapter: mapFalStatusToVideoStatus -> progress
      Adapter-->>App: VideoStatusResult
    end

    App->>Adapter: getVideoUrl(jobId)
    Adapter->>FAL: queue.result(jobId)
    FAL->>API: GET /queue/result
    API-->>FAL: { data: { video { url } } }
    FAL-->>Adapter: result
    Adapter->>Adapter: extract URL
    Adapter-->>App: VideoUrlResult
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • jherr

Poem

🐇 I hopped through types and adapters bright,

Fal.ai now joins the TanStack light.
Prompts and jobs, URLs in hand,
I stitched new adapters across the land.
A tiny rabbit cheers this build tonight ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Introduce fal.ai adapter for image and video generation' directly and accurately summarizes the main change: adding a new package with adapters for both image and video generation using fal.ai.

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

✨ Finishing touches
  • 📝 Generate docstrings

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
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: 4

🤖 Fix all issues with AI agents
In @.gitignore:
- Line 57: The .gitignore entry for .claude/settings.local.json conflicts with
the fact that that same file was committed; either stop tracking the local
settings file or stop ignoring it. Fix by removing the committed
.claude/settings.local.json from the repo index (so it remains only locally) and
commit that removal while keeping the .gitignore entry, or if it should be
shared, delete the .gitignore entry and rename the file to a shared name (e.g.,
settings.json) and commit the renamed file; ensure the change is committed and
the file is no longer tracked if choosing to ignore it.

In `@packages/typescript/ai-fal/package.json`:
- Around line 43-53: The package.json currently lists "@tanstack/ai" under
"dependencies" and "peerDependencies"; remove the "@tanstack/ai" entry from the
"dependencies" object so it only appears in "peerDependencies" (keep the
existing "workspace:*" value there) to ensure the adapter declares the framework
as a peer requirement and avoids bundling a duplicate dependency. Update the
"dependencies" section to no longer include the "@tanstack/ai" key while leaving
"@fal-ai/client" and all devDependencies unchanged.

In `@packages/typescript/ai-fal/src/adapters/image.ts`:
- Around line 20-22: falImage currently ignores FalImageConfig.apiKey and always
reads the API key from env, so passing apiKey in the config has no effect;
update the falImage factory (function falImage) to prefer and use the provided
config.apiKey (FalImageConfig.apiKey) as an override before falling back to
process.env.FAL_API_KEY when instantiating the Fal client or building requests,
and ensure any FalClient creation code (references to FalClientConfig) uses that
resolved key; alternatively, if you want to disallow passing the key, remove
apiKey from FalImageConfig, but the recommended fix is to honor config.apiKey.

In `@packages/typescript/ai-fal/src/adapters/video.ts`:
- Around line 17-19: falVideo currently ignores FalVideoConfig.apiKey and always
reads the API key from env; update falVideo to prefer the provided config.apiKey
as an override (e.g., use config.apiKey if present, otherwise fall back to
process.env.FAL_API_KEY) when creating the client, and ensure FalVideoConfig
(which extends FalClientConfig) remains usable; locate the falVideo
factory/constructor and replace the env-only lookup with a conditional that uses
FalVideoConfig.apiKey before falling back to the environment variable so passing
apiKey in config takes effect.
🧹 Nitpick comments (6)
packages/typescript/ai-fal/package.json (1)

15-20: Consider adding /adapters subpath export for tree-shaking.

Based on learnings, the package should export tree-shakeable adapters with clear subpath exports (e.g., @tanstack/ai-fal/adapters). Currently, only the root export is defined. This allows consumers to import only what they need without pulling in the entire bundle.

♻️ Suggested exports structure
 "exports": {
   ".": {
     "types": "./dist/esm/index.d.ts",
     "import": "./dist/esm/index.js"
-  }
+  },
+  "./adapters": {
+    "types": "./dist/esm/adapters/index.d.ts",
+    "import": "./dist/esm/adapters/index.js"
+  }
 },
packages/typescript/ai-fal/src/image/image-provider-options.ts (1)

50-61: DRY improvement: Use a const array to derive the type.

The preset names are duplicated: once in FalImageSizePreset type (lines 5-11) and again in this validation array. Consider using a const array to derive the type, eliminating the duplication.

♻️ Suggested refactor
+const FAL_IMAGE_SIZE_PRESETS = [
+  'square_hd',
+  'square',
+  'landscape_4_3',
+  'landscape_16_9',
+  'portrait_4_3',
+  'portrait_16_9',
+] as const
+
-export type FalImageSizePreset =
-  | 'square_hd'
-  | 'square'
-  | 'landscape_4_3'
-  | 'landscape_16_9'
-  | 'portrait_4_3'
-  | 'portrait_16_9'
+export type FalImageSizePreset = (typeof FAL_IMAGE_SIZE_PRESETS)[number]

 // ... later in mapSizeToFalFormat:
-  if (
-    [
-      'square_hd',
-      'square',
-      'landscape_4_3',
-      'landscape_16_9',
-      'portrait_4_3',
-      'portrait_16_9',
-    ].includes(size)
-  ) {
+  if ((FAL_IMAGE_SIZE_PRESETS as readonly string[]).includes(size)) {
     return size as FalImageSizePreset
   }
packages/typescript/ai-fal/src/utils/client.ts (2)

42-52: Document that proxyUrl takes precedence over apiKey.

When proxyUrl is provided, apiKey is ignored entirely. This is likely intentional (the proxy handles authentication), but this behavior should be documented in the interface or function JSDoc to avoid confusion, especially since apiKey is marked as required in FalClientConfig.

📝 Suggested documentation
 export interface FalClientConfig {
+  /**
+   * API key for fal.ai authentication.
+   * Ignored when proxyUrl is provided (proxy handles auth).
+   */
   apiKey: string
+  /**
+   * Optional proxy URL. When provided, takes precedence over apiKey
+   * for client configuration.
+   */
   proxyUrl?: string
 }

54-56: generateId may produce variable-length random suffixes.

Math.random().toString(36).substring(7) can produce strings of varying lengths (typically 5-6 characters) because small random numbers result in shorter base-36 representations. For consistent ID lengths, consider using substring(2, 9) to always get 7 characters.

♻️ Suggested fix for consistent length
 export function generateId(prefix: string): string {
-  return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`
+  return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`
 }
packages/typescript/ai-fal/src/adapters/image.ts (1)

91-124: Reuse the shared size mapping util to avoid drift.
This method duplicates logic already exported from image/image-provider-options.ts (also used in public API/tests). Consider delegating to the shared helper to keep mappings consistent.

♻️ Suggested refactor
 import {
   configureFalClient,
   getFalApiKeyFromEnv,
   generateId as utilGenerateId,
 } from '../utils'
+import { mapSizeToFalFormat } from '../image/image-provider-options'
@@
-    if (size) {
-      input.image_size = this.mapSizeToFalFormat(size)
-    }
+    if (size) {
+      input.image_size = mapSizeToFalFormat(size) ?? size
+    }
@@
-  private mapSizeToFalFormat(
-    size: string,
-  ): string | { width: number; height: number } {
-    const SIZE_TO_FAL_PRESET: Record<string, string> = {
-      '1024x1024': 'square_hd',
-      '512x512': 'square',
-      '1024x768': 'landscape_4_3',
-      '768x1024': 'portrait_4_3',
-      '1280x720': 'landscape_16_9',
-      '720x1280': 'portrait_16_9',
-      '1920x1080': 'landscape_16_9',
-      '1080x1920': 'portrait_16_9',
-    }
-
-    const preset = SIZE_TO_FAL_PRESET[size]
-    if (preset) return preset
-
-    const match = size.match(/^(\d+)x(\d+)$/)
-    if (match && match[1] && match[2]) {
-      return {
-        width: parseInt(match[1], 10),
-        height: parseInt(match[2], 10),
-      }
-    }
-
-    return size
-  }
+  // remove private mapSizeToFalFormat in favor of shared util
packages/typescript/ai-fal/tests/video-adapter.test.ts (1)

146-191: Consider adding edge case and failure status tests.

The status mapping tests cover the happy paths well. However, consider adding tests for:

  1. Edge cases for progress calculation (e.g., queue_position: 0 or queue_position: 15)
  2. Failed/error status handling if the Fal API supports failure states

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