-
-
Notifications
You must be signed in to change notification settings - Fork 115
feat: Introduce fal.ai adapter for image and video generation #237
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
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>
📝 WalkthroughWalkthroughAdds a new TypeScript package Changes
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
There was a problem hiding this 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/adapterssubpath 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
FalImageSizePresettype (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 thatproxyUrltakes precedence overapiKey.When
proxyUrlis provided,apiKeyis 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 sinceapiKeyis marked as required inFalClientConfig.📝 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:generateIdmay 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 usingsubstring(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 fromimage/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 utilpackages/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:
- Edge cases for progress calculation (e.g.,
queue_position: 0orqueue_position: 15)- Failed/error status handling if the Fal API supports failure states
Summary
@tanstack/ai-falpackage with image and video generation adaptersEndpointTypeMap) for autocomplete on 600+ modelsmodelOptionsthat exclude fields TanStack AI handles (prompt, size, etc.)Test plan
pnpm test:typespassespnpm test:libpasses (27 tests)pnpm buildsucceeds🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.