feat: Introduce fal.ai adapter for image and video generation#237
feat: Introduce fal.ai adapter for image and video generation#237tombeckenham wants to merge 34 commits intoTanStack:mainfrom
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>
📝 WalkthroughWalkthroughThis PR introduces a new Changes
Sequence Diagram(s)sequenceDiagram
participant User as User (Browser)
participant Component as ImageGenerator Component
participant Server as Server Functions
participant FalAdapter as Fal Image Adapter
participant FalAPI as Fal.ai API
User->>Component: Enter prompt & select model(s)
User->>Component: Click Generate
Component->>Component: Set loading state
Component->>Server: generateImage({prompt, model})
Server->>FalAdapter: new FalImageAdapter(model)
Server->>FalAdapter: generateImages({prompt, modelOptions})
FalAdapter->>FalAdapter: buildInput(prompt, size, modelOptions)
FalAdapter->>FalAPI: fal.subscribe(model, input)
FalAPI-->>FalAdapter: streaming result with image URLs
FalAdapter->>FalAdapter: transformResponse(result)
FalAdapter-->>Server: ImageGenerationResult {images: [...]}
Server-->>Component: {images, model}
Component->>Component: Update per-model results
Component->>User: Display generated images
sequenceDiagram
participant User as User (Browser)
participant Component as VideoGenerator Component
participant Server as Server Functions
participant FalAdapter as Fal Video Adapter
participant FalAPI as Fal.ai Queue API
User->>Component: Select video mode & model
User->>Component: Enter prompt (+ image for image-to-video)
User->>Component: Click Generate
Component->>Component: Set job to "submitting"
Component->>Server: createVideoJob({prompt, model, imageUrl?})
Server->>FalAdapter: createFalVideo(model, config)
Server->>FalAdapter: createVideoJob({prompt, duration, modelOptions})
FalAdapter->>FalAdapter: buildInput(prompt, size, modelOptions)
FalAdapter->>FalAPI: fal.queue.submit(model, input)
FalAPI-->>FalAdapter: {jobId}
FalAdapter-->>Server: VideoJobResult {jobId, model}
Server-->>Component: {jobId, model}
Component->>Component: Set job to "pending", start polling
loop Poll Status (every N seconds)
Component->>Server: getVideoStatus({jobId, model})
Server->>FalAdapter: getVideoStatus(jobId)
FalAdapter->>FalAPI: fal.queue.status(jobId)
FalAPI-->>FalAdapter: {status: IN_QUEUE|IN_PROGRESS|COMPLETED, queue_position?, ...}
FalAdapter->>FalAdapter: mapFalStatusToVideoStatus(fal_status)
FalAdapter-->>Server: {status: pending|processing|completed, progress?}
Server-->>Component: VideoStatusResult
Component->>Component: Update per-model status & progress
alt Status === completed
Component->>Server: getVideoUrl({jobId, model})
Server->>FalAdapter: getVideoUrl(jobId)
FalAdapter->>FalAPI: fal.queue.result(jobId)
FalAPI-->>FalAdapter: {video_url or video.url}
FalAdapter-->>Server: VideoUrlResult {url}
Server-->>Component: {url}
Component->>Component: Stop polling, display video
Component->>User: Show playable video
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
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.
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.
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.
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()
|
@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! |
|
I was thinking that. I've got an idea for a demo. |
|
There is a testing panel that tests image and video APIs (really all the APIs) |
… Also logged a ticket with fal
|
I'm working on this. I've discovered this afternoon that the fal client is out of sync with what the fal api supports. I'll need a bit more time on this |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@scripts/compare-fal-models.ts`:
- Around line 242-244: Change the array type annotation in generateCSV to comply
with the lint rule by replacing the short form string[] with the generic form
Array<string>; specifically update the declaration of the lines variable (and
any other occurrences inside generateCSV using string[] types) to use
Array<string> so the function signature and internal typings remain consistent
with the project's ESLint configuration.
In `@scripts/fetch-fal-models.ts`:
- Around line 124-126: The inline comment above the sleep call is misleading —
it says "5 seconds" while the code calls sleep(2000) (2 seconds); update the
comment to match the actual delay or, if the intended delay is 5s, change the
argument to sleep(5000). Locate the block that checks cursor and calls sleep in
scripts/fetch-fal-models.ts (the cursor check and sleep(...) call) and make the
comment and the sleep() value consistent.
- Around line 1-6: The file header comment incorrectly states the script saves a
"TypeScript file" while scripts/fetch-fal-models.ts actually writes JSON output;
update the top comment block to say the script downloads models from the Fal API
and saves them as a generated JSON file (or "JSON output") so the description
matches the actual behavior, and adjust any phrasing referencing "TypeScript
file" to "JSON file" in that header.
🧹 Nitpick comments (1)
scripts/compare-fal-models.ts (1)
15-16: Use dynamic module resolution instead of hard-coded node_modules path for EndpointTypeMap.The current path at line 81 assumes a specific workspace layout (
packages/typescript/ai-fal/node_modules/@fal-ai/client/src/types/endpoints.d.ts) and is fragile across different pnpm configurations. Since@fal-ai/clientis published withsrc/types/endpoints.d.tsavailable via typesVersions, resolve it dynamically:Suggested approach
-import { join } from 'node:path' +import { dirname, join } from 'node:path' +import { createRequire } from 'node:module' @@ - const typesPath = join( - process.cwd(), - 'packages/typescript/ai-fal/node_modules/@fal-ai/client/src/types/endpoints.d.ts', - ) + const require = createRequire(import.meta.url) + const pkgJsonPath = require.resolve('@fal-ai/client/package.json') + const typesPath = join( + dirname(pkgJsonPath), + 'src/types/endpoints.d.ts', + )Also applies to: 77-83, 87-88
…s from input types Changed the endpoint map generation script to derive output types directly from input types (replacing "Input" suffix with "Output") instead of parsing complex GET response type definitions with regex. This eliminates all warnings caused by inconsistent formatting in generated type files. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…rmatting - Fix ESLint import/no-duplicates error by combining imports from './index' - Add Zod schema type annotations to all SchemaMap constants - Use specific union types (FalImageModel, FalTextModel, etc.) instead of string in combined schema maps - Add Prettier formatting to all generated files - Reorder Model type definition before SchemaMap (since SchemaMap references it) - Remove unused postTypeName variable All generated files now pass ESLint and TypeScript checks with proper type safety. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@packages/typescript/ai-fal/src/adapters/video.ts`:
- Line 21: The FalVideoConfig interface currently omits apiKey while JSDoc
claims an apiKey override is supported; either restore an optional apiKey in
FalVideoConfig (e.g., extend FalClientConfig but allow apiKey?: string) so
callers can pass an override, or update the JSDoc/comments around FalVideoConfig
(and related usage around lines ~180-190) to state that apiKey is only read from
the environment and cannot be provided via config; update any runtime code that
reads config.apiKey (e.g., where FalVideoConfig is consumed) to match the chosen
behavior.
… scripts - Refactored file path handling in compare-fal-models.ts and fetch-fal-models.ts to use __dirname for better compatibility with module types. - Ensured the scripts directory is created if it doesn't exist. - Cleaned up import statements in various files for consistency and clarity. - Enhanced type imports in generated index.ts to reflect the new structure of endpoint maps. These changes improve the maintainability and reliability of the scripts.
tombeckenham
left a comment
There was a problem hiding this comment.
I'm a bit happier with where this is at. It now gathers type information from fal using their open api spec, then generates both types and zod schemas. These will be super useful as you can use Ai to transform prompts to optimise for each model. It will also allow users to update UI at runtime based on what options a model will allow.
I'm stil not totally happy with the size mapping, and I need to create an example app. I'm unfortunately busy now until Monday. I'll take a look then
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@packages/typescript/ai-fal/src/adapters/image.ts`:
- Around line 89-103: The generateImages method is currently defaulting size to
'0x0' which causes image_size, aspect_ratio, and resolution to be set to
placeholder/null values and sent to fal.ai; update generateImages so that when
size is undefined or parseSize returns an invalid size you omit image_size,
aspect_ratio, and resolution from the object passed to this.inputSchema.parse.
Concretely, build the input payload by spreading modelOptions and required
fields (prompt, num_images), then conditionally add image_size:
this.mapSizeToImageSize(...), aspect_ratio: this.calculateAspectRatio(...), and
resolution: this.determineResolution(...) only if a valid size was provided
(i.e., size !== undefined and parseSize returns non-zero width/height) before
calling this.inputSchema.parse.
In `@packages/typescript/ai-fal/src/adapters/video.ts`:
- Around line 80-101: createVideoJob currently parses the input using the
adapter's schema (this.inputSchema) but submits the request using options.model,
which can cause schema/model mismatches; update the method to use the adapter's
model (this.model) when calling fal.queue.submit or else validate that
options.model === this.model before submission, and throw or coerce if they
differ so the parsed input and submitted model stay consistent (refer to
createVideoJob, this.inputSchema, this.model, and options.model).
…nsforms - Consolidate input/output categories (e.g., text-to-image + image-to-image → image) - Add x-fal-file-input extension to mark URL fields for Blob/File uploads - Add TypeScript typeTransformer to generate string | Blob | File unions - Add Zod resolver for file input fields and format:binary output fields - Update endpoint map generation for new category structure - Remove deprecated fetch/compare scripts from root package.json Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@packages/typescript/ai-fal/openapi-ts.config.ts`:
- Line 265: Remove the stray debug console.log by deleting or wrapping
console.log(groupedCategoryFilenames) so it only runs under a debug/verbose
flag; update the surrounding code in openapi-ts.config.ts where
groupedCategoryFilenames is logged (or replace with
processLogger.debug/process.env.DEBUG check) to prevent spamming build logs.
🧹 Nitpick comments (2)
packages/typescript/ai-fal/openapi-ts.config.ts (2)
168-187: Consider usingfor...ofto avoid forEach return value warning.The static analyzer flags line 172 because
findAllRefsreturns a value. While this is a false positive (the return is intentionally ignored and mutation happens via the sharedrefsSet), usingfor...ofis more idiomatic for imperative side-effect loops.♻️ Proposed refactor
if (Array.isArray(obj)) { - obj.forEach((item) => findAllRefs(item, refs)) + for (const item of obj) { + findAllRefs(item, refs) + } } else {
281-281: Consider usingfor...offor consistency.Same pattern as line 172 -
Set.add()returns the Set, triggering the static analysis warning. Usingfor...ofwould be consistent with the earlier suggestion and avoid the warning.♻️ Proposed refactor
- unknown.forEach((u) => allUnknown.add(u)) + for (const u of unknown) { + allUnknown.add(u) + }
- Changed file_data type from Blob | File to string in various schema definitions for consistency. - Updated Zod schemas to reflect the new string type for file_data. - Adjusted input and output schemas across multiple categories to ensure type uniformity. These changes enhance type safety and simplify handling of file data in the application.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@packages/typescript/ai-fal/openapi-ts.config.ts`:
- Around line 172-174: Convert the concise arrow callbacks used with
Array.prototype.forEach to block-bodied callbacks to avoid returning values from
the callback; specifically change the forEach in findAllRefs from
obj.forEach((item) => findAllRefs(item, refs)) to obj.forEach((item) => {
findAllRefs(item, refs); }); Do the same for the other forEach callback flagged
by the linter elsewhere in this file (the similar arrow callback around the
later block) so each callback uses a block body and an explicit statement rather
than an expression return.
- Around line 347-377: The string(ctx: any) Zod resolver currently returns
undefined, disabling file-type unions; re-enable the commented blocks so inputs
with schema['x-fal-file-input'] return a z.union of z.string(), instanceof Blob
and instanceof File (using $(z).attr('union').call($.array(...))) and outputs
with schema.format === 'binary' return a z.union of instanceof Blob and
instanceof File similarly; use the existing locals ({ $, schema, symbols } and z
from symbols) and ensure the resolver returns those union expressions instead of
undefined so runtime validation matches the TypeScript types.
🧹 Nitpick comments (1)
packages/typescript/ai-fal/openapi-ts.config.ts (1)
23-48: Consider camelCase for the new constants.
KNOWN_MISSING_SCHEMAS(Line 23) andFAL_FILE_FIELD_PATTERNS(Line 40) use SCREAMING_SNAKE_CASE; considerknownMissingSchemas/falFileFieldPatterns(and update references) to align with the naming rule. As per coding guidelines, use camelCase for function and variable names throughout the codebase.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@packages/typescript/ai-fal/src/adapters/image.ts`:
- Around line 199-204: The type FalImageModel is undefined and should be
replaced with the existing FalModel; update both factory functions (the
createFalImage generic signatures and returned FalImageAdapter instantiations)
so their generic constraint reads TModel extends FalModel and the
FalImageAdapter is parameterized with that TModel, ensuring you rely on the
already-imported FalModel used elsewhere.
- Around line 77-90: The code extracts size but never uses it and several helper
symbols (parseSize, mapSizeToImageSize, calculateAspectRatio,
determineResolution, SIZE_TO_PRESET) are dead; implement size handling by
parsing the incoming size (using parseSize), mapping it to the fal.ai image size
preset (via mapSizeToImageSize or SIZE_TO_PRESET), and include the resulting
size/resolution field in the fal.subscribe input object (alongside prompt,
num_images and modelOptions) before calling fal.subscribe(this.model, ...);
ensure any resolution/aspect logic uses calculateAspectRatio and
determineResolution as needed, or if you choose to omit size support remove
SIZE_TO_PRESET, parseSize, mapSizeToImageSize, calculateAspectRatio,
determineResolution and drop size from the destructuring so no unused code
remains.
- Around line 80-82: The unconditional throw for modelOptions prevents using
prompt-only image generation; remove the hard throw in adapters/image.ts and
treat modelOptions as optional by merging it with sensible defaults (e.g.,
defaultModelOptions) before use, update the surrounding function (the image
adapter / whichever function reads modelOptions) to accept modelOptions?: Type
and only validate required subfields when they are actually necessary, and
ensure downstream calls reference the merged options object instead of the raw
modelOptions variable.
🧹 Nitpick comments (2)
packages/typescript/ai-fal/src/model-meta.ts (1)
14-14: Remove unusedFalModelTesttype.This type appears to be leftover debugging/test code and is not used anywhere in the codebase. Consider removing it to keep the public API surface clean.
-export type FalModelTest = 'fal-ai/nano-banana-pro/edit' export type FalModel = keyof EndpointTypeMappackages/typescript/ai-fal/src/adapters/image.ts (1)
164-172: Avoidanytype for better type safety.The
imgparameter usesanywhich loses type inference. Consider using a minimal inline type:- images = data.images.map((img: any) => this.parseImage(img)) + images = data.images.map((img: { url: string }) => this.parseImage(img))
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@packages/typescript/ai-fal/src/adapters/image.ts`:
- Line 72: Update the inline comment that currently reads "TB Jan 20206" to the
correct date "TB Jan 2026" in packages/typescript/ai-fal/src/adapters/image.ts;
locate the comment containing the exact text "TB Jan 20206 I can think of no way
to use satisfies here because of the union type of FalModelInput with record
any" and correct the year to 2026.
- Around line 69-80: The buildInput method currently assigns options.size
directly to image_size; update buildInput (in the
ImageGenerationOptions->buildInput flow) to convert the size using
mapSizeToFalFormat before assigning to FalModelInput.image_size — import
mapSizeToFalFormat and replace image_size: options.size with image_size:
mapSizeToFalFormat(options.size) so fal.ai receives preset names or
{width,height} instead of the TanStack "1024x1024" string.
- Around line 51-59: The constructor currently ignores config.apiKey and only
uses getFalApiKeyFromEnv(); update the FalImageAdapter constructor to honor
config.apiKey by resolving apiKey = config?.apiKey ?? getFalApiKeyFromEnv() (or
simply pass the whole config into configureFalClient(config) like
FalVideoAdapter does), then validate apiKey and call configureFalClient({
apiKey, proxyUrl: config?.proxyUrl }) (or configureFalClient(config)) so the
adapter respects FalClientConfig.apiKey and still falls back to environment.
- Around line 42-47: The adapter is using broad Record types for
model->options/sizes which incorrectly assert a single-model-specific value for
all FalModel keys; update FalImageAdapter's BaseImageAdapter generic arguments
to use the concrete mappings FalImageModelProviderOptionsByName and
FalImageModelSizeByName (instead of Record<FalModel,
FalImageProviderOptions<TModel>> and Record<FalModel,
FalModelImageSize<TModel>>), and adjust the FalImageAdapter generic parameters
if needed so the second/third/fourth type args reference
FalImageProviderOptions, FalImageModelProviderOptionsByName, and
FalImageModelSizeByName respectively (matching the pattern used by
OpenAIImageAdapter) to ensure proper per-model typing.
🧹 Nitpick comments (1)
packages/typescript/ai-fal/src/adapters/image.ts (1)
86-108: Consider logging or throwing when response contains neitherimagesnorimage.If the fal.ai response doesn't match either expected format,
transformResponsesilently returns an emptyimagesarray. This could mask API errors or unexpected response structures.💡 Suggested improvement
// Handle single image response (some models return { image: {...} }) else if ('image' in data && data.image && typeof data.image === 'object') { images.push(this.parseImage(data.image)) } + else if (images.length === 0) { + console.warn( + `[fal-ai] Unexpected response format: neither 'images' array nor 'image' object found`, + ) + } return {
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@packages/typescript/ai-fal/src/model-meta.ts`:
- Around line 24-27: FalModelInput currently falls back to Record<string, any>,
losing compile-time safety—change its fallback to Record<string, unknown> so
unknown model inputs remain type-safe; then refactor FalModelImageSize to mirror
the conditional on EndpointTypeMap (i.e., check TModel extends keyof
EndpointTypeMap) and return EndpointTypeMap[TModel]['imageSize'] for known
models and never for unknown models (instead of inheriting the any fallback);
update any consumers relying on the old any fallback to handle the new
never/unknown semantics.
- Around line 11-15: FalModel currently uses "keyof EndpointTypeMap | string"
which collapses to string and loses autocomplete; change the type to use the
string & {} trick so known keys are preserved while still allowing arbitrary
strings (i.e., update the FalModel type declaration referencing EndpointTypeMap
to use "keyof EndpointTypeMap | (string & {})"); keep the existing JSDoc comment
and export name FalModel intact so editor tooling continues to offer known model
completions.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@examples/ts-react-fal/src/components/VideoGenerator.tsx`:
- Around line 46-52: The Biome lint error comes from using a concise arrow in
pollingRefs.current.forEach which creates an implicit return; update the
callbacks to use a block body that explicitly returns void (e.g., replace
pollingRefs.current.forEach((interval) => clearInterval(interval)) with a
block-style callback that calls clearInterval(interval) inside {}), and then
call pollingRefs.current.clear() as before; apply the same change to the other
occurrence that iterates pollingRefs.current (the useEffect cleanup around the
polling logic) so all forEach callbacks use explicit block bodies to satisfy the
linter.
In `@examples/ts-react-fal/src/lib/server-functions.ts`:
- Around line 5-8: The inputValidator callback currently calls
data.prompt.trim() and can throw a TypeError for non-string inputs; update the
validator used in .inputValidator to first check types (e.g., ensure typeof
data.prompt === 'string' and typeof data.model === 'string' or data.prompt !=
null) and throw the intended validation Errors ("Prompt is required", "Model is
required") when the checks fail, then call trim() and return data; apply the
same type-guarding change to the other validator at the location referenced
(lines 82-86) so both validators validate types before calling trim().
In `@examples/ts-react-fal/src/styles.css`:
- Around line 3-5: The CSS uses the Tailwind directive "@apply m-0" in
styles.css which causes a Biome parse error; fix by either enabling tailwind
directives in your Biome config (set tailwindDirectives: true) so Biome accepts
"@apply", or remove/replace the "@apply m-0" usage with equivalent plain CSS
(e.g., set margin: 0 on the body selector) to avoid Tailwind directives
entirely.
🧹 Nitpick comments (2)
examples/ts-react-fal/src/lib/models.ts (1)
1-85: Rename model constants to camelCase for guideline compliance.
IMAGE_MODELS/VIDEO_MODELSviolate the camelCase variable naming rule. Consider renaming to camelCase and updating references in consuming files.♻️ Suggested refactor
-export const IMAGE_MODELS = [ +export const imageModels = [ @@ -] as const +] as const @@ -export const VIDEO_MODELS = [ +export const videoModels = [ @@ -] as const +] as const @@ -export type ImageModel = (typeof IMAGE_MODELS)[number] -export type VideoModel = (typeof VIDEO_MODELS)[number] +export type ImageModel = (typeof imageModels)[number] +export type VideoModel = (typeof videoModels)[number]As per coding guidelines,
**/*.{ts,tsx,js,jsx}: Use camelCase for function and variable names throughout the codebase.examples/ts-react-fal/src/lib/prompts.ts (1)
3-81: Rename prompt constants to camelCase and keep return types precise.
IMAGE_PROMPTS/VIDEO_PROMPTSbreak the camelCase naming rule, and the helpers could returnImagePrompt/VideoPromptto preserve the literal union types. Update consuming imports accordingly.♻️ Suggested refactor
-export const IMAGE_PROMPTS = [ +export const imagePrompts = [ @@ -] as const +] as const @@ -export const VIDEO_PROMPTS = { +export const videoPrompts = { @@ -} as const +} as const @@ -export type ImagePrompt = (typeof IMAGE_PROMPTS)[number] +export type ImagePrompt = (typeof imagePrompts)[number] export type VideoPrompt = - | (typeof VIDEO_PROMPTS.textToVideo)[number] - | (typeof VIDEO_PROMPTS.imageToVideo)[number] + | (typeof videoPrompts.textToVideo)[number] + | (typeof videoPrompts.imageToVideo)[number] @@ -export function getRandomImagePrompt(): string { - return IMAGE_PROMPTS[Math.floor(Math.random() * IMAGE_PROMPTS.length)] +export function getRandomImagePrompt(): ImagePrompt { + return imagePrompts[Math.floor(Math.random() * imagePrompts.length)] } @@ -export function getRandomVideoPrompt( - mode: 'text-to-video' | 'image-to-video', -): string { +export function getRandomVideoPrompt( + mode: 'text-to-video' | 'image-to-video', +): VideoPrompt { const prompts = mode === 'text-to-video' - ? VIDEO_PROMPTS.textToVideo - : VIDEO_PROMPTS.imageToVideo + ? videoPrompts.textToVideo + : videoPrompts.imageToVideo return prompts[Math.floor(Math.random() * prompts.length)] }As per coding guidelines,
**/*.{ts,tsx,js,jsx}: Use camelCase for function and variable names throughout the codebase.

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
Chores