@umituz/react-native-ai-fal-provider 1.0.58 → 1.0.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-fal-provider",
3
- "version": "1.0.58",
3
+ "version": "1.0.60",
4
4
  "description": "FAL AI provider for React Native - implements IAIProvider interface for unified AI generation",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
package/src/index.ts CHANGED
@@ -36,6 +36,27 @@ export {
36
36
  buildReplaceBackgroundInput, buildHDTouchUpInput,
37
37
  } from "./infrastructure/utils";
38
38
 
39
+ export {
40
+ isFalModelType, isModelType, isFalErrorType,
41
+ isValidBase64Image, isValidApiKey, isValidModelId, isValidPrompt,
42
+ isValidTimeout, isValidRetryCount,
43
+ } from "./infrastructure/utils";
44
+
45
+ export {
46
+ formatImageDataUri, extractBase64, getDataUriExtension, isImageDataUri,
47
+ calculateTimeoutWithJitter, formatCreditCost, truncatePrompt, sanitizePrompt,
48
+ buildErrorMessage, isDefined, removeNullish, debounce, throttle,
49
+ } from "./infrastructure/utils";
50
+
51
+ export {
52
+ createJobMetadata, updateJobMetadata, isJobCompleted, isJobRunning,
53
+ isJobStale, getJobDuration, formatJobDuration, calculateJobProgress,
54
+ serializeJobMetadata, deserializeJobMetadata, filterValidJobs,
55
+ sortJobsByCreation, getActiveJobs, getCompletedJobs,
56
+ } from "./infrastructure/utils";
57
+
58
+ export type { FalJobMetadata } from "./infrastructure/utils";
59
+
39
60
  export type {
40
61
  UpscaleOptions, PhotoRestoreOptions, FaceSwapOptions, ImageToImagePromptConfig,
41
62
  RemoveBackgroundOptions, RemoveObjectOptions, ReplaceBackgroundOptions,
@@ -106,6 +106,7 @@ export class FalProvider implements IAIProvider {
106
106
  this.validateInitialization();
107
107
  const timeoutMs = options?.timeoutMs ?? this.config?.defaultTimeoutMs ?? DEFAULT_FAL_CONFIG.defaultTimeoutMs;
108
108
  let timeoutId: ReturnType<typeof setTimeout> | null = null;
109
+ let currentRequestId: string | null = null;
109
110
 
110
111
  if (typeof __DEV__ !== "undefined" && __DEV__) {
111
112
  console.log("[FalProvider] Subscribe started:", { model, timeoutMs });
@@ -119,12 +120,16 @@ export class FalProvider implements IAIProvider {
119
120
  input,
120
121
  logs: false,
121
122
  pollInterval: DEFAULT_FAL_CONFIG.pollInterval,
122
- onQueueUpdate: (update: { status: string; logs?: unknown[] }) => {
123
- const jobStatus = mapFalStatusToJobStatus(update as unknown as FalQueueStatus);
123
+ onQueueUpdate: (update: { status: string; logs?: unknown[]; request_id?: string }) => {
124
+ currentRequestId = update.request_id ?? null;
125
+ const jobStatus = mapFalStatusToJobStatus({
126
+ ...update as unknown as FalQueueStatus,
127
+ requestId: currentRequestId ?? "",
128
+ });
124
129
  if (jobStatus.status !== lastStatus) {
125
130
  lastStatus = jobStatus.status;
126
131
  if (typeof __DEV__ !== "undefined" && __DEV__) {
127
- console.log("[FalProvider] Status:", jobStatus.status);
132
+ console.log("[FalProvider] Status:", jobStatus.status, "RequestId:", currentRequestId);
128
133
  }
129
134
  }
130
135
  options?.onQueueUpdate?.(jobStatus);
@@ -136,7 +141,7 @@ export class FalProvider implements IAIProvider {
136
141
  ]);
137
142
 
138
143
  if (typeof __DEV__ !== "undefined" && __DEV__) {
139
- console.log("[FalProvider] Subscribe completed:", { model });
144
+ console.log("[FalProvider] Subscribe completed:", { model, requestId: currentRequestId });
140
145
  }
141
146
 
142
147
  validateNSFWContent(result as Record<string, unknown>);
@@ -6,21 +6,29 @@
6
6
  import type { JobStatus, AIJobStatusType } from "@umituz/react-native-ai-generation-content";
7
7
  import type { FalQueueStatus, FalLogEntry } from "../../domain/entities/fal.types";
8
8
 
9
- const STATUS_MAP: Record<string, AIJobStatusType> = {
10
- IN_QUEUE: "IN_QUEUE",
11
- IN_PROGRESS: "IN_PROGRESS",
12
- COMPLETED: "COMPLETED",
13
- FAILED: "FAILED",
14
- };
9
+ const STATUS_MAP = {
10
+ IN_QUEUE: "IN_QUEUE" as const,
11
+ IN_PROGRESS: "IN_PROGRESS" as const,
12
+ COMPLETED: "COMPLETED" as const,
13
+ FAILED: "FAILED" as const,
14
+ } as const satisfies Record<string, AIJobStatusType>;
15
15
 
16
+ const DEFAULT_STATUS: AIJobStatusType = "IN_PROGRESS";
17
+
18
+ /**
19
+ * Map FAL queue status to standardized job status
20
+ * Provides safe defaults for missing or invalid values
21
+ */
16
22
  export function mapFalStatusToJobStatus(status: FalQueueStatus): JobStatus {
23
+ const mappedStatus = STATUS_MAP[status.status] ?? DEFAULT_STATUS;
24
+
17
25
  return {
18
- status: STATUS_MAP[status.status] ?? "IN_PROGRESS",
26
+ status: mappedStatus,
19
27
  logs: status.logs?.map((log: FalLogEntry) => ({
20
28
  message: log.message,
21
29
  level: log.level ?? "info",
22
- timestamp: log.timestamp,
23
- })),
24
- queuePosition: status.queuePosition,
30
+ timestamp: log.timestamp ?? new Date().toISOString(),
31
+ })) ?? [],
32
+ queuePosition: status.queuePosition ?? undefined,
25
33
  };
26
34
  }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Helper Utilities
3
+ * Common helper functions for FAL operations
4
+ */
5
+
6
+ /**
7
+ * Format image as data URI if not already formatted
8
+ */
9
+ export function formatImageDataUri(base64: string): string {
10
+ if (base64.startsWith("data:")) {
11
+ return base64;
12
+ }
13
+ return `data:image/jpeg;base64,${base64}`;
14
+ }
15
+
16
+ /**
17
+ * Extract base64 from data URI
18
+ */
19
+ export function extractBase64(dataUri: string): string {
20
+ if (!dataUri.startsWith("data:")) {
21
+ return dataUri;
22
+ }
23
+
24
+ const parts = dataUri.split(",");
25
+ return parts.length > 1 ? parts[1] : dataUri;
26
+ }
27
+
28
+ /**
29
+ * Get file extension from data URI
30
+ */
31
+ export function getDataUriExtension(dataUri: string): string | null {
32
+ const match = dataUri.match(/^data:image\/(\w+);base64/);
33
+ return match ? match[1] : null;
34
+ }
35
+
36
+ /**
37
+ * Check if data URI is an image
38
+ */
39
+ export function isImageDataUri(value: string): boolean {
40
+ return value.startsWith("data:image/");
41
+ }
42
+
43
+ /**
44
+ * Calculate timeout with jitter to avoid thundering herd
45
+ */
46
+ export function calculateTimeoutWithJitter(
47
+ baseTimeout: number,
48
+ jitterPercent: number = 0.1
49
+ ): number {
50
+ const jitter = baseTimeout * jitterPercent;
51
+ const randomJitter = Math.random() * jitter - jitter / 2;
52
+ return Math.max(1000, baseTimeout + randomJitter);
53
+ }
54
+
55
+ /**
56
+ * Format credit cost for display
57
+ */
58
+ export function formatCreditCost(cost: number): string {
59
+ if (cost % 1 === 0) {
60
+ return cost.toString();
61
+ }
62
+ return cost.toFixed(2);
63
+ }
64
+
65
+ /**
66
+ * Truncate prompt to maximum length
67
+ */
68
+ export function truncatePrompt(prompt: string, maxLength: number = 5000): string {
69
+ if (prompt.length <= maxLength) {
70
+ return prompt;
71
+ }
72
+ return prompt.slice(0, maxLength - 3) + "...";
73
+ }
74
+
75
+ /**
76
+ * Sanitize prompt by removing excessive whitespace
77
+ */
78
+ export function sanitizePrompt(prompt: string): string {
79
+ return prompt.trim().replace(/\s+/g, " ");
80
+ }
81
+
82
+ /**
83
+ * Build error message with context
84
+ */
85
+ export function buildErrorMessage(
86
+ type: string,
87
+ context: Record<string, unknown>
88
+ ): string {
89
+ const contextStr = Object.entries(context)
90
+ .map(([key, value]) => `${key}=${JSON.stringify(value)}`)
91
+ .join(", ");
92
+ return `${type}${contextStr ? ` (${contextStr})` : ""}`;
93
+ }
94
+
95
+ /**
96
+ * Check if value is defined (not null or undefined)
97
+ */
98
+ export function isDefined<T>(value: T | null | undefined): value is T {
99
+ return value !== null && value !== undefined;
100
+ }
101
+
102
+ /**
103
+ * Filter out null and undefined values from object
104
+ */
105
+ export function removeNullish<T extends Record<string, unknown>>(
106
+ obj: T
107
+ ): Partial<T> {
108
+ return Object.fromEntries(
109
+ Object.entries(obj).filter(([_, value]) => isDefined(value))
110
+ ) as Partial<T>;
111
+ }
112
+
113
+ /**
114
+ * Debounce function (for rate limiting)
115
+ */
116
+ export function debounce<T extends (...args: never[]) => unknown>(
117
+ func: T,
118
+ wait: number
119
+ ): (...args: Parameters<T>) => void {
120
+ let timeout: ReturnType<typeof setTimeout> | null = null;
121
+
122
+ return function executedFunction(...args: Parameters<T>) {
123
+ const later = () => {
124
+ timeout = null;
125
+ func(...args);
126
+ };
127
+
128
+ if (timeout) {
129
+ clearTimeout(timeout);
130
+ }
131
+ timeout = setTimeout(later, wait);
132
+ };
133
+ }
134
+
135
+ /**
136
+ * Simple throttle function
137
+ */
138
+ export function throttle<T extends (...args: never[]) => unknown>(
139
+ func: T,
140
+ limit: number
141
+ ): (...args: Parameters<T>) => void {
142
+ let inThrottle = false;
143
+
144
+ return function executedFunction(...args: Parameters<T>) {
145
+ if (!inThrottle) {
146
+ func(...args);
147
+ inThrottle = true;
148
+ setTimeout(() => (inThrottle = false), limit);
149
+ }
150
+ };
151
+ }
@@ -18,3 +18,50 @@ export {
18
18
  buildReplaceBackgroundInput,
19
19
  buildHDTouchUpInput,
20
20
  } from "./input-builders.util";
21
+
22
+ export {
23
+ isFalModelType,
24
+ isModelType,
25
+ isFalErrorType,
26
+ isValidBase64Image,
27
+ isValidApiKey,
28
+ isValidModelId,
29
+ isValidPrompt,
30
+ isValidTimeout,
31
+ isValidRetryCount,
32
+ } from "./type-guards.util";
33
+
34
+ export {
35
+ formatImageDataUri,
36
+ extractBase64,
37
+ getDataUriExtension,
38
+ isImageDataUri,
39
+ calculateTimeoutWithJitter,
40
+ formatCreditCost,
41
+ truncatePrompt,
42
+ sanitizePrompt,
43
+ buildErrorMessage,
44
+ isDefined,
45
+ removeNullish,
46
+ debounce,
47
+ throttle,
48
+ } from "./helpers.util";
49
+
50
+ export {
51
+ createJobMetadata,
52
+ updateJobMetadata,
53
+ isJobCompleted,
54
+ isJobRunning,
55
+ isJobStale,
56
+ getJobDuration,
57
+ formatJobDuration,
58
+ calculateJobProgress,
59
+ serializeJobMetadata,
60
+ deserializeJobMetadata,
61
+ filterValidJobs,
62
+ sortJobsByCreation,
63
+ getActiveJobs,
64
+ getCompletedJobs,
65
+ } from "./job-metadata.util";
66
+
67
+ export type { FalJobMetadata } from "./job-metadata.util";
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Job Metadata Utilities
3
+ * Helper functions for job metadata and management
4
+ */
5
+
6
+ import type { JobStatus } from "@umituz/react-native-ai-generation-content";
7
+
8
+ /**
9
+ * Job metadata for tracking and persistence
10
+ */
11
+ export interface FalJobMetadata {
12
+ readonly requestId: string;
13
+ readonly model: string;
14
+ readonly status: JobStatus["status"];
15
+ readonly createdAt: string;
16
+ readonly updatedAt: string;
17
+ readonly completedAt?: string;
18
+ readonly timeout?: number;
19
+ readonly error?: string;
20
+ }
21
+
22
+ /**
23
+ * Create job metadata
24
+ */
25
+ export function createJobMetadata(
26
+ requestId: string,
27
+ model: string,
28
+ timeout?: number
29
+ ): FalJobMetadata {
30
+ const now = new Date().toISOString();
31
+ return {
32
+ requestId,
33
+ model,
34
+ status: "IN_QUEUE",
35
+ createdAt: now,
36
+ updatedAt: now,
37
+ timeout,
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Update job metadata status
43
+ */
44
+ export function updateJobMetadata(
45
+ metadata: FalJobMetadata,
46
+ status: JobStatus["status"],
47
+ error?: string
48
+ ): FalJobMetadata {
49
+ return {
50
+ ...metadata,
51
+ status,
52
+ updatedAt: new Date().toISOString(),
53
+ ...(status === "COMPLETED" || status === "FAILED" ? { completedAt: new Date().toISOString() } : {}),
54
+ ...(error ? { error } : {}),
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Check if job is completed (success or failure)
60
+ */
61
+ export function isJobCompleted(metadata: FalJobMetadata): boolean {
62
+ return metadata.status === "COMPLETED" || metadata.status === "FAILED";
63
+ }
64
+
65
+ /**
66
+ * Check if job is running
67
+ */
68
+ export function isJobRunning(metadata: FalJobMetadata): boolean {
69
+ return metadata.status === "IN_QUEUE" || metadata.status === "IN_PROGRESS";
70
+ }
71
+
72
+ /**
73
+ * Check if job is stale (older than specified minutes)
74
+ */
75
+ export function isJobStale(metadata: FalJobMetadata, maxAgeMinutes: number = 60): boolean {
76
+ const age = Date.now() - new Date(metadata.createdAt).getTime();
77
+ const maxAgeMs = maxAgeMinutes * 60 * 1000;
78
+ return age > maxAgeMs;
79
+ }
80
+
81
+ /**
82
+ * Get job duration in milliseconds
83
+ */
84
+ export function getJobDuration(metadata: FalJobMetadata): number | null {
85
+ if (!metadata.completedAt) {
86
+ return null;
87
+ }
88
+ return new Date(metadata.completedAt).getTime() - new Date(metadata.createdAt).getTime();
89
+ }
90
+
91
+ /**
92
+ * Format job duration for display
93
+ */
94
+ export function formatJobDuration(metadata: FalJobMetadata): string {
95
+ const duration = getJobDuration(metadata);
96
+ if (!duration) {
97
+ return "In progress";
98
+ }
99
+
100
+ const seconds = Math.floor(duration / 1000);
101
+ if (seconds < 60) {
102
+ return `${seconds}s`;
103
+ }
104
+
105
+ const minutes = Math.floor(seconds / 60);
106
+ const remainingSeconds = seconds % 60;
107
+ return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
108
+ }
109
+
110
+ /**
111
+ * Calculate job progress percentage
112
+ */
113
+ export function calculateJobProgress(metadata: FalJobMetadata): number {
114
+ switch (metadata.status) {
115
+ case "IN_QUEUE":
116
+ return 10;
117
+ case "IN_PROGRESS":
118
+ return 50;
119
+ case "COMPLETED":
120
+ return 100;
121
+ case "FAILED":
122
+ return 0;
123
+ default:
124
+ return 0;
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Serialize job metadata for storage
130
+ */
131
+ export function serializeJobMetadata(metadata: FalJobMetadata): string {
132
+ return JSON.stringify(metadata);
133
+ }
134
+
135
+ /**
136
+ * Deserialize job metadata from storage
137
+ */
138
+ export function deserializeJobMetadata(data: string): FalJobMetadata | null {
139
+ try {
140
+ return JSON.parse(data) as FalJobMetadata;
141
+ } catch {
142
+ return null;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Filter valid job metadata from array
148
+ */
149
+ export function filterValidJobs(jobs: FalJobMetadata[]): FalJobMetadata[] {
150
+ return jobs.filter((job) => !isJobStale(job));
151
+ }
152
+
153
+ /**
154
+ * Sort jobs by creation time (newest first)
155
+ */
156
+ export function sortJobsByCreation(jobs: FalJobMetadata[]): FalJobMetadata[] {
157
+ return [...jobs].sort((a, b) => {
158
+ const timeA = new Date(a.createdAt).getTime();
159
+ const timeB = new Date(b.createdAt).getTime();
160
+ return timeB - timeA;
161
+ });
162
+ }
163
+
164
+ /**
165
+ * Get active jobs (not completed and not stale)
166
+ */
167
+ export function getActiveJobs(jobs: FalJobMetadata[]): FalJobMetadata[] {
168
+ return jobs.filter((job) => isJobRunning(job) && !isJobStale(job));
169
+ }
170
+
171
+ /**
172
+ * Get completed jobs
173
+ */
174
+ export function getCompletedJobs(jobs: FalJobMetadata[]): FalJobMetadata[] {
175
+ return jobs.filter((job) => isJobCompleted(job));
176
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Type Guards and Validation Utilities
3
+ * Runtime type checking and validation helpers
4
+ */
5
+
6
+ import type { FalModelType } from "../../domain/entities/fal.types";
7
+ import type { ModelType } from "../../domain/types/model-selection.types";
8
+ import { FalErrorType } from "../../domain/entities/error.types";
9
+
10
+ /**
11
+ * Check if a string is a valid FalModelType
12
+ */
13
+ export function isFalModelType(value: unknown): value is FalModelType {
14
+ const validTypes: ReadonlyArray<FalModelType> = [
15
+ "text-to-image",
16
+ "text-to-video",
17
+ "text-to-voice",
18
+ "image-to-video",
19
+ "image-to-image",
20
+ "text-to-text",
21
+ ];
22
+ return typeof value === "string" && validTypes.includes(value as FalModelType);
23
+ }
24
+
25
+ /**
26
+ * Check if a string is a valid ModelType
27
+ */
28
+ export function isModelType(value: unknown): value is ModelType {
29
+ const validTypes: ReadonlyArray<ModelType> = [
30
+ "text-to-image",
31
+ "text-to-video",
32
+ "image-to-video",
33
+ "text-to-voice",
34
+ ];
35
+ return typeof value === "string" && validTypes.includes(value as ModelType);
36
+ }
37
+
38
+ /**
39
+ * Check if error is a FalErrorType
40
+ */
41
+ export function isFalErrorType(value: unknown): value is FalErrorType {
42
+ const validTypes: ReadonlyArray<FalErrorType> = [
43
+ FalErrorType.NETWORK,
44
+ FalErrorType.TIMEOUT,
45
+ FalErrorType.API_ERROR,
46
+ FalErrorType.VALIDATION,
47
+ FalErrorType.CONTENT_POLICY,
48
+ FalErrorType.RATE_LIMIT,
49
+ FalErrorType.AUTHENTICATION,
50
+ FalErrorType.QUOTA_EXCEEDED,
51
+ FalErrorType.MODEL_NOT_FOUND,
52
+ FalErrorType.UNKNOWN,
53
+ ];
54
+ return typeof value === "string" && validTypes.includes(value as FalErrorType);
55
+ }
56
+
57
+ /**
58
+ * Validate base64 image string
59
+ */
60
+ export function isValidBase64Image(value: unknown): boolean {
61
+ if (typeof value !== "string") {
62
+ return false;
63
+ }
64
+
65
+ // Check data URI prefix
66
+ if (value.startsWith("data:image/")) {
67
+ return value.includes("base64,");
68
+ }
69
+
70
+ // Check if it's a valid base64 string
71
+ const base64Pattern = /^[A-Za-z0-9+/]+=*$/;
72
+ return base64Pattern.test(value) && value.length > 0;
73
+ }
74
+
75
+ /**
76
+ * Validate API key format
77
+ */
78
+ export function isValidApiKey(value: unknown): boolean {
79
+ return typeof value === "string" && value.length > 0;
80
+ }
81
+
82
+ /**
83
+ * Validate model ID format
84
+ */
85
+ export function isValidModelId(value: unknown): boolean {
86
+ if (typeof value !== "string") {
87
+ return false;
88
+ }
89
+
90
+ // FAL model IDs typically follow the pattern: "owner/model-name" or "owner/model/version"
91
+ const modelIdPattern = /^[a-z0-9-]+\/[a-z0-9-]+(\/[a-z0-9.]+)?$/;
92
+ return modelIdPattern.test(value);
93
+ }
94
+
95
+ /**
96
+ * Validate prompt string
97
+ */
98
+ export function isValidPrompt(value: unknown): boolean {
99
+ return typeof value === "string" && value.trim().length > 0 && value.length <= 5000;
100
+ }
101
+
102
+ /**
103
+ * Validate timeout value
104
+ */
105
+ export function isValidTimeout(value: unknown): boolean {
106
+ return typeof value === "number" && value > 0 && value <= 600000; // Max 10 minutes
107
+ }
108
+
109
+ /**
110
+ * Validate retry count
111
+ */
112
+ export function isValidRetryCount(value: unknown): boolean {
113
+ return typeof value === "number" && value >= 0 && value <= 10;
114
+ }
@@ -20,6 +20,7 @@ export interface UseFalGenerationResult<T> {
20
20
  error: FalErrorInfo | null;
21
21
  isLoading: boolean;
22
22
  isRetryable: boolean;
23
+ requestId: string | null;
23
24
  generate: (modelEndpoint: string, input: FalJobInput) => Promise<T | null>;
24
25
  retry: () => Promise<T | null>;
25
26
  reset: () => void;
@@ -33,6 +34,7 @@ export function useFalGeneration<T = unknown>(
33
34
  const [isLoading, setIsLoading] = useState(false);
34
35
 
35
36
  const lastRequestRef = useRef<{ endpoint: string; input: FalJobInput } | null>(null);
37
+ const currentRequestIdRef = useRef<string | null>(null);
36
38
 
37
39
  const generate = useCallback(
38
40
  async (modelEndpoint: string, input: FalJobInput): Promise<T | null> => {
@@ -40,15 +42,18 @@ export function useFalGeneration<T = unknown>(
40
42
  setIsLoading(true);
41
43
  setError(null);
42
44
  setData(null);
45
+ currentRequestIdRef.current = null;
43
46
 
44
47
  try {
45
48
  const result = await falProvider.subscribe<T>(modelEndpoint, input, {
46
49
  timeoutMs: options?.timeoutMs,
47
50
  onQueueUpdate: (status) => {
51
+ // Note: requestId is tracked internally by falProvider subscribe
52
+ // and exposed via the requestId ref, not from status object
48
53
  // Map JobStatus to FalQueueStatus for backward compatibility
49
54
  options?.onProgress?.({
50
55
  status: status.status,
51
- requestId: "",
56
+ requestId: currentRequestIdRef.current ?? "",
52
57
  logs: status.logs?.map((log: FalLogEntry) => ({
53
58
  message: log.message,
54
59
  level: log.level,
@@ -84,6 +89,7 @@ export function useFalGeneration<T = unknown>(
84
89
  setError(null);
85
90
  setIsLoading(false);
86
91
  lastRequestRef.current = null;
92
+ currentRequestIdRef.current = null;
87
93
  }, []);
88
94
 
89
95
  return {
@@ -91,6 +97,7 @@ export function useFalGeneration<T = unknown>(
91
97
  error,
92
98
  isLoading,
93
99
  isRetryable: error ? isFalErrorRetryable(error.originalError) : false,
100
+ requestId: currentRequestIdRef.current,
94
101
  generate,
95
102
  retry,
96
103
  reset,