-
-
Notifications
You must be signed in to change notification settings - Fork 116
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: { image(s) }, 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 Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 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
AlemTuzlak
left a 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.
Mostly looks good, left a few comments
|
View your CI Pipeline Execution ↗ for commit 0295f19
☁️ Nx Cloud last updated this comment at |
@tanstack/ai
@tanstack/ai-anthropic
@tanstack/ai-client
@tanstack/ai-devtools-core
@tanstack/ai-fal
@tanstack/ai-gemini
@tanstack/ai-grok
@tanstack/ai-ollama
@tanstack/ai-openai
@tanstack/ai-openrouter
@tanstack/ai-preact
@tanstack/ai-react
@tanstack/ai-react-ui
@tanstack/ai-solid
@tanstack/ai-solid-ui
@tanstack/ai-svelte
@tanstack/ai-vue
@tanstack/ai-vue-ui
@tanstack/preact-ai-devtools
@tanstack/react-ai-devtools
@tanstack/solid-ai-devtools
commit: |
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: 3
🤖 Fix all issues with AI agents
In `@packages/typescript/ai-fal/src/adapters/image.ts`:
- Around line 57-112: The private mapSizeToFalFormat method duplicates shared
logic and incorrectly returns unknown sizes instead of undefined; remove this
private mapSizeToFalFormat and import the shared mapSizeToFalFormat from
image-provider-options.ts, then in generateImages (where input.image_size is
set) only include image_size when the imported utility returns a defined value
(i.e., set ...(size && mappedSize ? { image_size: mappedSize } : {}) ), ensuring
invalid sizes are filtered out before calling fal.subscribe.
In `@packages/typescript/ai-fal/src/adapters/video.ts`:
- Around line 75-86: The payload in createVideoJob sometimes includes
aspect_ratio: undefined because sizeToAspectRatio(size) can return undefined;
change the input construction in createVideoJob to call
this.sizeToAspectRatio(size) into a local variable (e.g., const aspect =
this.sizeToAspectRatio(size)) and only include aspect_ratio in the FalModelInput
if aspect is not undefined (i.e., spread { aspect_ratio: aspect }
conditionally), so the API never receives aspect_ratio: undefined; update
references to FalModelInput<TModel> and ensure other fields (prompt, duration,
modelOptions) remain unchanged.
- Around line 105-110: The progress calculation currently uses a truthy check on
statusResponse.queue_position which treats 0 as falsy and yields undefined;
update the return in the function that builds the response (the object
containing jobId and mapFalStatusToVideoStatus(...)) to use a nullish check
(e.g., check statusResponse.queue_position != null or use ??) so that 0 is
preserved and progress computes to Math.max(0, 100 -
statusResponse.queue_position * 10).
♻️ Duplicate comments (2)
packages/typescript/ai-fal/src/adapters/video.ts (1)
168-178:config.apiKeyis documented but not supported.
FalVideoConfigomitsapiKey, andfalVideoalways reads from env. Either addapiKey?: stringand prefer it, or update the docs to avoid a misleading API.🔧 Option: honor config.apiKey
-export interface FalVideoConfig extends Omit<FalClientConfig, 'apiKey'> {} +export interface FalVideoConfig extends Omit<FalClientConfig, 'apiKey'> { + apiKey?: string +} @@ - const apiKey = getFalApiKeyFromEnv() + const apiKey = config?.apiKey ?? getFalApiKeyFromEnv()packages/typescript/ai-fal/src/adapters/image.ts (1)
194-199:config.apiKeyis documented but not supported.
FalImageConfigomitsapiKey, andfalImagealways reads from env. Either addapiKey?: stringand prefer it, or update the docs to avoid a misleading API.🔧 Option: honor config.apiKey
-export interface FalImageConfig extends Omit<FalClientConfig, 'apiKey'> {} +export interface FalImageConfig extends Omit<FalClientConfig, 'apiKey'> { + apiKey?: string +} @@ - const apiKey = getFalApiKeyFromEnv() + const apiKey = config?.apiKey ?? getFalApiKeyFromEnv()
| async generateImages( | ||
| options: ImageGenerationOptions<FalImageProviderOptions<TModel>>, | ||
| ): Promise<ImageGenerationResult> { | ||
| const { model, prompt, numberOfImages, size, modelOptions } = options | ||
|
|
||
| // Build the input object - spread modelOptions first, then override with standard options | ||
| const input: FalModelInput<TModel> = { | ||
| ...modelOptions, | ||
| prompt, | ||
| ...(size ? { image_size: this.mapSizeToFalFormat(size) } : {}), | ||
| ...(numberOfImages ? { num_images: numberOfImages } : {}), | ||
| } | ||
|
|
||
| const result = await fal.subscribe(model as TModel, { input }) | ||
|
|
||
| return this.transformResponse(model, result) | ||
| } | ||
|
|
||
| protected override generateId(): string { | ||
| return utilGenerateId(this.name) | ||
| } | ||
|
|
||
| /** | ||
| * Maps TanStack AI size format (WIDTHxHEIGHT) to fal.ai format. | ||
| * fal.ai accepts either preset names or { width, height } objects. | ||
| */ | ||
| 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', | ||
| } | ||
|
|
||
| // Check if it's a known preset mapping | ||
| const preset = SIZE_TO_FAL_PRESET[size] | ||
| if (preset) return preset | ||
|
|
||
| // Try to parse as WIDTHxHEIGHT | ||
| 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 as-is if it's already a preset name | ||
| return size | ||
| } |
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.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, let's explore the structure of the ai-fal package
find packages/typescript/ai-fal -type f -name "*.ts" | head -20Repository: TanStack/ai
Length of output: 166
🏁 Script executed:
rg "mapSizeToFalFormat" packages/typescript/ai-fal/Repository: TanStack/ai
Length of output: 2042
🏁 Script executed:
ls -la packages/typescript/ai-fal/src/image/ 2>/dev/null || echo "Directory does not exist"Repository: TanStack/ai
Length of output: 240
🏁 Script executed:
cat -n packages/typescript/ai-fal/src/image/image-provider-options.tsRepository: TanStack/ai
Length of output: 1914
🏁 Script executed:
cat -n packages/typescript/ai-fal/src/adapters/image.ts | sed -n '80,115p'Repository: TanStack/ai
Length of output: 1353
🏁 Script executed:
grep -A 20 "describe('mapSizeToFalFormat'" packages/typescript/ai-fal/tests/image-adapter.test.tsRepository: TanStack/ai
Length of output: 932
🏁 Script executed:
# Check if mapSizeToFalFormat is exported from index.ts
grep -A 2 "mapSizeToFalFormat" packages/typescript/ai-fal/src/index.tsRepository: TanStack/ai
Length of output: 143
🏁 Script executed:
# Verify the current usage context in adapters/image.ts
cat -n packages/typescript/ai-fal/src/adapters/image.ts | sed -n '57,75p'Repository: TanStack/ai
Length of output: 877
Replace the private mapSizeToFalFormat() method with the shared utility to ensure consistent validation.
The codebase has a shared mapSizeToFalFormat() exported from image-provider-options.ts that properly validates sizes and returns undefined for invalid inputs. The private implementation duplicates this logic but differs in a critical way: it passes unknown sizes through as-is instead of returning undefined, which can forward invalid values to fal.ai. Import and use the shared utility instead, setting image_size only when a valid value is returned.
🔧 Suggested refactor
-import type { OutputType, Result } from '@fal-ai/client'
+import type { OutputType, Result } from '@fal-ai/client'
+import { mapSizeToFalFormat } from '../image/image-provider-options'
@@
- const input: FalModelInput<TModel> = {
- ...modelOptions,
- prompt,
- ...(size ? { image_size: this.mapSizeToFalFormat(size) } : {}),
- ...(numberOfImages ? { num_images: numberOfImages } : {}),
- }
+ const imageSize = size ? mapSizeToFalFormat(size) : undefined
+ const input: FalModelInput<TModel> = {
+ ...modelOptions,
+ prompt,
+ ...(imageSize ? { image_size: imageSize } : {}),
+ ...(numberOfImages ? { num_images: numberOfImages } : {}),
+ }
@@
- 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
- }🤖 Prompt for AI Agents
In `@packages/typescript/ai-fal/src/adapters/image.ts` around lines 57 - 112, The
private mapSizeToFalFormat method duplicates shared logic and incorrectly
returns unknown sizes instead of undefined; remove this private
mapSizeToFalFormat and import the shared mapSizeToFalFormat from
image-provider-options.ts, then in generateImages (where input.image_size is
set) only include image_size when the imported utility returns a defined value
(i.e., set ...(size && mappedSize ? { image_size: mappedSize } : {}) ), ensuring
invalid sizes are filtered out before calling fal.subscribe.
| async createVideoJob( | ||
| options: VideoGenerationOptions<FalVideoProviderOptions<TModel>>, | ||
| ): Promise<VideoJobResult> { | ||
| const { model, prompt, size, duration, modelOptions } = options | ||
|
|
||
| // Build the input object for fal.ai | ||
| const input: FalModelInput<TModel> = { | ||
| ...modelOptions, | ||
| prompt, | ||
| ...(duration ? { duration } : {}), | ||
| ...(size ? { aspect_ratio: this.sizeToAspectRatio(size) } : {}), | ||
| } |
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.
🧩 Analysis chain
🏁 Script executed:
# First, let's look at the video.ts file to understand the context better
head -100 packages/typescript/ai-fal/src/adapters/video.ts | cat -nRepository: TanStack/ai
Length of output: 3355
🏁 Script executed:
# Search for the sizeToAspectRatio method implementation
rg "sizeToAspectRatio" packages/typescript/ai-fal/src/adapters/video.ts -A 10 -B 2Repository: TanStack/ai
Length of output: 838
🏁 Script executed:
# Check the FalModelInput type definition
rg "FalModelInput" packages/typescript/ai-fal/src/ -A 5 -B 2Repository: TanStack/ai
Length of output: 4752
🏁 Script executed:
# Look for similar patterns with conditional spreads in the codebase
rg "\.\.\.\(" packages/typescript/ai-fal/src/ -B 2 -A 1 | head -50Repository: TanStack/ai
Length of output: 880
Avoid sending aspect_ratio: undefined to the fal.ai API.
When size is provided but not in WIDTHxHEIGHT format, sizeToAspectRatio() returns undefined, yet the spread operator still includes aspect_ratio: undefined in the payload, which the API may reject.
🔧 Suggested change
- const input: FalModelInput<TModel> = {
- ...modelOptions,
- prompt,
- ...(duration ? { duration } : {}),
- ...(size ? { aspect_ratio: this.sizeToAspectRatio(size) } : {}),
- }
+ const aspectRatio = size ? this.sizeToAspectRatio(size) : undefined
+ const input: FalModelInput<TModel> = {
+ ...modelOptions,
+ prompt,
+ ...(duration ? { duration } : {}),
+ ...(aspectRatio ? { aspect_ratio: aspectRatio } : {}),
+ }📝 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.
| async createVideoJob( | |
| options: VideoGenerationOptions<FalVideoProviderOptions<TModel>>, | |
| ): Promise<VideoJobResult> { | |
| const { model, prompt, size, duration, modelOptions } = options | |
| // Build the input object for fal.ai | |
| const input: FalModelInput<TModel> = { | |
| ...modelOptions, | |
| prompt, | |
| ...(duration ? { duration } : {}), | |
| ...(size ? { aspect_ratio: this.sizeToAspectRatio(size) } : {}), | |
| } | |
| async createVideoJob( | |
| options: VideoGenerationOptions<FalVideoProviderOptions<TModel>>, | |
| ): Promise<VideoJobResult> { | |
| const { model, prompt, size, duration, modelOptions } = options | |
| // Build the input object for fal.ai | |
| const aspectRatio = size ? this.sizeToAspectRatio(size) : undefined | |
| const input: FalModelInput<TModel> = { | |
| ...modelOptions, | |
| prompt, | |
| ...(duration ? { duration } : {}), | |
| ...(aspectRatio ? { aspect_ratio: aspectRatio } : {}), | |
| } |
🤖 Prompt for AI Agents
In `@packages/typescript/ai-fal/src/adapters/video.ts` around lines 75 - 86, The
payload in createVideoJob sometimes includes aspect_ratio: undefined because
sizeToAspectRatio(size) can return undefined; change the input construction in
createVideoJob to call this.sizeToAspectRatio(size) into a local variable (e.g.,
const aspect = this.sizeToAspectRatio(size)) and only include aspect_ratio in
the FalModelInput if aspect is not undefined (i.e., spread { aspect_ratio:
aspect } conditionally), so the API never receives aspect_ratio: undefined;
update references to FalModelInput<TModel> and ensure other fields (prompt,
duration, modelOptions) remain unchanged.
| return { | ||
| jobId, | ||
| status: mapFalStatusToVideoStatus(statusResponse.status), | ||
| progress: statusResponse.queue_position | ||
| ? Math.max(0, 100 - statusResponse.queue_position * 10) | ||
| : undefined, |
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.
🧩 Analysis chain
🏁 Script executed:
cat -n packages/typescript/ai-fal/src/adapters/video.ts | sed -n '100,115p'Repository: TanStack/ai
Length of output: 653
🏁 Script executed:
rg "FalStatusResponse|queue_position" packages/typescript/ai-fal/src/ -A 3 -B 1Repository: TanStack/ai
Length of output: 1339
Use nullish check instead of truthy check for queue_position.
When queue_position = 0, the current truthy check treats it as falsy, causing progress to become undefined. Since 0 is a valid value that should calculate to progress = 100, use a nullish check to preserve it.
🔧 Suggested change
- progress: statusResponse.queue_position
- ? Math.max(0, 100 - statusResponse.queue_position * 10)
- : undefined,
+ progress:
+ statusResponse.queue_position != null
+ ? Math.max(0, 100 - statusResponse.queue_position * 10)
+ : undefined,📝 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.
| return { | |
| jobId, | |
| status: mapFalStatusToVideoStatus(statusResponse.status), | |
| progress: statusResponse.queue_position | |
| ? Math.max(0, 100 - statusResponse.queue_position * 10) | |
| : undefined, | |
| return { | |
| jobId, | |
| status: mapFalStatusToVideoStatus(statusResponse.status), | |
| progress: | |
| statusResponse.queue_position != null | |
| ? Math.max(0, 100 - statusResponse.queue_position * 10) | |
| : undefined, |
🤖 Prompt for AI Agents
In `@packages/typescript/ai-fal/src/adapters/video.ts` around lines 105 - 110, The
progress calculation currently uses a truthy check on
statusResponse.queue_position which treats 0 as falsy and yields undefined;
update the return in the function that builds the response (the object
containing jobId and mapFalStatusToVideoStatus(...)) to use a nullish check
(e.g., check statusResponse.queue_position != null or use ??) so that 0 is
preserved and progress computes to Math.max(0, 100 -
statusResponse.queue_position * 10).
|
@tombeckenham would you mind confirming that my PR didn't break anything and image/video gen still works, would really appreciate some sort of small demo app in examples repo to be able to see and test it! |
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.