@tanstack/ai-fal 0.6.6 → 0.6.9
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.
|
@@ -50,9 +50,23 @@ class FalVideoAdapter extends BaseVideoAdapter {
|
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
async getVideoUrl(jobId) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
let result;
|
|
54
|
+
try {
|
|
55
|
+
result = await fal.queue.result(this.model, {
|
|
56
|
+
requestId: jobId
|
|
57
|
+
});
|
|
58
|
+
} catch (error) {
|
|
59
|
+
const detail = error?.body?.detail;
|
|
60
|
+
if (Array.isArray(detail)) {
|
|
61
|
+
const messages = detail.map(
|
|
62
|
+
(d) => d.loc ? `${d.loc.join(".")}: ${d.msg}` : d.msg
|
|
63
|
+
);
|
|
64
|
+
throw new Error(`Video generation failed: ${messages.join("; ")}`);
|
|
65
|
+
}
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Failed to retrieve video result: ${error.message || error}`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
56
70
|
const data = result.data;
|
|
57
71
|
const url = data.video?.url || data.video_url;
|
|
58
72
|
if (!url) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video.js","sources":["../../../src/adapters/video.ts"],"sourcesContent":["import { fal } from '@fal-ai/client'\nimport { BaseVideoAdapter } from '@tanstack/ai/adapters'\nimport { configureFalClient, generateId as utilGenerateId } from '../utils'\nimport { mapVideoSizeToFalFormat } from '../video/video-provider-options'\nimport type {\n VideoGenerationOptions,\n VideoJobResult,\n VideoStatusResult,\n VideoUrlResult,\n} from '@tanstack/ai'\nimport type {\n FalModel,\n FalModelInput,\n FalModelVideoSize,\n FalVideoProviderOptions,\n} from '../model-meta'\nimport type { FalClientConfig } from '../utils'\n\ntype FalQueueStatus = 'IN_QUEUE' | 'IN_PROGRESS' | 'COMPLETED'\n\ninterface FalStatusResponse {\n status: FalQueueStatus\n queue_position?: number\n logs?: Array<{ message: string }>\n}\n\ninterface FalVideoResultData {\n video?: { url: string }\n video_url?: string\n}\n\n/**\n * Maps fal.ai queue status to TanStack AI video status.\n */\nfunction mapFalStatusToVideoStatus(\n falStatus: FalQueueStatus,\n): VideoStatusResult['status'] {\n switch (falStatus) {\n case 'IN_QUEUE':\n return 'pending'\n case 'IN_PROGRESS':\n return 'processing'\n case 'COMPLETED':\n return 'completed'\n default:\n return 'processing'\n }\n}\n\n/**\n * fal.ai video generation adapter.\n * Supports MiniMax, Luma, Kling, Hunyuan, and other fal.ai video models.\n *\n * Uses fal.ai's comprehensive type system to provide autocomplete\n * and type safety for all supported video models.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport class FalVideoAdapter<TModel extends FalModel> extends BaseVideoAdapter<\n TModel,\n FalVideoProviderOptions<TModel>,\n Record<TModel, FalVideoProviderOptions<TModel>>,\n Record<TModel, FalModelVideoSize<TModel>>\n> {\n readonly kind = 'video' as const\n readonly name = 'fal' as const\n\n constructor(model: TModel, config?: FalClientConfig) {\n super({}, model)\n configureFalClient(config)\n }\n\n async createVideoJob(\n options: VideoGenerationOptions<\n FalVideoProviderOptions<TModel>,\n FalModelVideoSize<TModel>\n >,\n ): Promise<VideoJobResult> {\n const { prompt, size, duration, modelOptions } = options\n const sizeParams = mapVideoSizeToFalFormat(size)\n\n const input = {\n ...modelOptions,\n ...sizeParams,\n prompt,\n ...(duration ? { duration } : {}),\n } as FalModelInput<TModel>\n\n // Submit to queue and get request ID\n const { request_id } = await fal.queue.submit(this.model, {\n input,\n })\n\n return {\n jobId: request_id,\n model: this.model,\n }\n }\n\n async getVideoStatus(jobId: string): Promise<VideoStatusResult> {\n const statusResponse = (await fal.queue.status(this.model, {\n requestId: jobId,\n logs: true,\n })) as FalStatusResponse\n\n return {\n jobId,\n status: mapFalStatusToVideoStatus(statusResponse.status),\n progress:\n statusResponse.queue_position != null\n ? Math.max(0, 100 - statusResponse.queue_position * 10)\n : undefined,\n }\n }\n\n async getVideoUrl(jobId: string): Promise<VideoUrlResult> {\n
|
|
1
|
+
{"version":3,"file":"video.js","sources":["../../../src/adapters/video.ts"],"sourcesContent":["import { fal } from '@fal-ai/client'\nimport { BaseVideoAdapter } from '@tanstack/ai/adapters'\nimport { configureFalClient, generateId as utilGenerateId } from '../utils'\nimport { mapVideoSizeToFalFormat } from '../video/video-provider-options'\nimport type {\n VideoGenerationOptions,\n VideoJobResult,\n VideoStatusResult,\n VideoUrlResult,\n} from '@tanstack/ai'\nimport type {\n FalModel,\n FalModelInput,\n FalModelVideoSize,\n FalVideoProviderOptions,\n} from '../model-meta'\nimport type { FalClientConfig } from '../utils'\n\ntype FalQueueStatus = 'IN_QUEUE' | 'IN_PROGRESS' | 'COMPLETED'\n\ninterface FalStatusResponse {\n status: FalQueueStatus\n queue_position?: number\n logs?: Array<{ message: string }>\n}\n\ninterface FalVideoResultData {\n video?: { url: string }\n video_url?: string\n}\n\n/**\n * Maps fal.ai queue status to TanStack AI video status.\n *\n * Note: fal.ai does not return a FAILED queue status. Errors surface\n * as exceptions when fetching results from a COMPLETED job (e.g. 422\n * validation errors). Those are handled in getVideoUrl().\n */\nfunction mapFalStatusToVideoStatus(\n falStatus: FalQueueStatus,\n): VideoStatusResult['status'] {\n switch (falStatus) {\n case 'IN_QUEUE':\n return 'pending'\n case 'IN_PROGRESS':\n return 'processing'\n case 'COMPLETED':\n return 'completed'\n default:\n return 'processing'\n }\n}\n\n/**\n * fal.ai video generation adapter.\n * Supports MiniMax, Luma, Kling, Hunyuan, and other fal.ai video models.\n *\n * Uses fal.ai's comprehensive type system to provide autocomplete\n * and type safety for all supported video models.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport class FalVideoAdapter<TModel extends FalModel> extends BaseVideoAdapter<\n TModel,\n FalVideoProviderOptions<TModel>,\n Record<TModel, FalVideoProviderOptions<TModel>>,\n Record<TModel, FalModelVideoSize<TModel>>\n> {\n readonly kind = 'video' as const\n readonly name = 'fal' as const\n\n constructor(model: TModel, config?: FalClientConfig) {\n super({}, model)\n configureFalClient(config)\n }\n\n async createVideoJob(\n options: VideoGenerationOptions<\n FalVideoProviderOptions<TModel>,\n FalModelVideoSize<TModel>\n >,\n ): Promise<VideoJobResult> {\n const { prompt, size, duration, modelOptions } = options\n const sizeParams = mapVideoSizeToFalFormat(size)\n\n const input = {\n ...modelOptions,\n ...sizeParams,\n prompt,\n ...(duration ? { duration } : {}),\n } as FalModelInput<TModel>\n\n // Submit to queue and get request ID\n const { request_id } = await fal.queue.submit(this.model, {\n input,\n })\n\n return {\n jobId: request_id,\n model: this.model,\n }\n }\n\n async getVideoStatus(jobId: string): Promise<VideoStatusResult> {\n const statusResponse = (await fal.queue.status(this.model, {\n requestId: jobId,\n logs: true,\n })) as FalStatusResponse\n\n return {\n jobId,\n status: mapFalStatusToVideoStatus(statusResponse.status),\n progress:\n statusResponse.queue_position != null\n ? Math.max(0, 100 - statusResponse.queue_position * 10)\n : undefined,\n }\n }\n\n async getVideoUrl(jobId: string): Promise<VideoUrlResult> {\n let result\n try {\n result = await fal.queue.result(this.model, {\n requestId: jobId,\n })\n } catch (error: any) {\n // fal.ai may report COMPLETED status but throw on result fetch\n // (e.g. 422 validation errors). Extract the detailed error info.\n const detail = error?.body?.detail\n if (Array.isArray(detail)) {\n const messages = detail.map(\n (d: { msg?: string; loc?: Array<string> }) =>\n d.loc ? `${d.loc.join('.')}: ${d.msg}` : d.msg,\n )\n throw new Error(`Video generation failed: ${messages.join('; ')}`)\n }\n throw new Error(\n `Failed to retrieve video result: ${error.message || error}`,\n )\n }\n\n const data = result.data as FalVideoResultData\n\n // Different models return video URL in different formats\n const url = data.video?.url || data.video_url\n if (!url) {\n throw new Error('Video URL not found in response')\n }\n\n return {\n jobId,\n url,\n }\n }\n\n protected override generateId(): string {\n return utilGenerateId(this.name)\n }\n}\n\n/**\n * Create a fal.ai video adapter with an explicit API key.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport function createFalVideo<TModel extends FalModel>(\n model: TModel,\n config?: FalClientConfig,\n): FalVideoAdapter<TModel> {\n return new FalVideoAdapter(model, config)\n}\n\n/**\n * Create a fal.ai video adapter using config.apiKey or the FAL_KEY environment variable.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport function falVideo<TModel extends FalModel>(\n model: TModel,\n config?: FalClientConfig,\n): FalVideoAdapter<TModel> {\n return createFalVideo(model, config)\n}\n"],"names":["utilGenerateId"],"mappings":";;;;AAsCA,SAAS,0BACP,WAC6B;AAC7B,UAAQ,WAAA;AAAA,IACN,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EAAA;AAEb;AAWO,MAAM,wBAAiD,iBAK5D;AAAA,EAIA,YAAY,OAAe,QAA0B;AACnD,UAAM,CAAA,GAAI,KAAK;AAJjB,SAAS,OAAO;AAChB,SAAS,OAAO;AAId,uBAAmB,MAAM;AAAA,EAC3B;AAAA,EAEA,MAAM,eACJ,SAIyB;AACzB,UAAM,EAAE,QAAQ,MAAM,UAAU,iBAAiB;AACjD,UAAM,aAAa,wBAAwB,IAAI;AAE/C,UAAM,QAAQ;AAAA,MACZ,GAAG;AAAA,MACH,GAAG;AAAA,MACH;AAAA,MACA,GAAI,WAAW,EAAE,aAAa,CAAA;AAAA,IAAC;AAIjC,UAAM,EAAE,eAAe,MAAM,IAAI,MAAM,OAAO,KAAK,OAAO;AAAA,MACxD;AAAA,IAAA,CACD;AAED,WAAO;AAAA,MACL,OAAO;AAAA,MACP,OAAO,KAAK;AAAA,IAAA;AAAA,EAEhB;AAAA,EAEA,MAAM,eAAe,OAA2C;AAC9D,UAAM,iBAAkB,MAAM,IAAI,MAAM,OAAO,KAAK,OAAO;AAAA,MACzD,WAAW;AAAA,MACX,MAAM;AAAA,IAAA,CACP;AAED,WAAO;AAAA,MACL;AAAA,MACA,QAAQ,0BAA0B,eAAe,MAAM;AAAA,MACvD,UACE,eAAe,kBAAkB,OAC7B,KAAK,IAAI,GAAG,MAAM,eAAe,iBAAiB,EAAE,IACpD;AAAA,IAAA;AAAA,EAEV;AAAA,EAEA,MAAM,YAAY,OAAwC;AACxD,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,IAAI,MAAM,OAAO,KAAK,OAAO;AAAA,QAC1C,WAAW;AAAA,MAAA,CACZ;AAAA,IACH,SAAS,OAAY;AAGnB,YAAM,SAAS,OAAO,MAAM;AAC5B,UAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,cAAM,WAAW,OAAO;AAAA,UACtB,CAAC,MACC,EAAE,MAAM,GAAG,EAAE,IAAI,KAAK,GAAG,CAAC,KAAK,EAAE,GAAG,KAAK,EAAE;AAAA,QAAA;AAE/C,cAAM,IAAI,MAAM,4BAA4B,SAAS,KAAK,IAAI,CAAC,EAAE;AAAA,MACnE;AACA,YAAM,IAAI;AAAA,QACR,oCAAoC,MAAM,WAAW,KAAK;AAAA,MAAA;AAAA,IAE9D;AAEA,UAAM,OAAO,OAAO;AAGpB,UAAM,MAAM,KAAK,OAAO,OAAO,KAAK;AACpC,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MAAM,iCAAiC;AAAA,IACnD;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEmB,aAAqB;AACtC,WAAOA,WAAe,KAAK,IAAI;AAAA,EACjC;AACF;AAOO,SAAS,eACd,OACA,QACyB;AACzB,SAAO,IAAI,gBAAgB,OAAO,MAAM;AAC1C;AAOO,SAAS,SACd,OACA,QACyB;AACzB,SAAO,eAAe,OAAO,MAAM;AACrC;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-fal",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.9",
|
|
4
4
|
"description": "fal.ai adapter for TanStack AI",
|
|
5
5
|
"author": "",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,15 +31,15 @@
|
|
|
31
31
|
"video-generation"
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@fal-ai/client": "^1.9.
|
|
34
|
+
"@fal-ai/client": "^1.9.4"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@vitest/coverage-v8": "4.0.14",
|
|
38
38
|
"vite": "^7.2.7",
|
|
39
|
-
"@tanstack/ai": "0.
|
|
39
|
+
"@tanstack/ai": "0.9.2"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@tanstack/ai": "0.
|
|
42
|
+
"@tanstack/ai": "0.9.2"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
45
|
"build": "vite build",
|
package/src/adapters/video.ts
CHANGED
|
@@ -31,6 +31,10 @@ interface FalVideoResultData {
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Maps fal.ai queue status to TanStack AI video status.
|
|
34
|
+
*
|
|
35
|
+
* Note: fal.ai does not return a FAILED queue status. Errors surface
|
|
36
|
+
* as exceptions when fetching results from a COMPLETED job (e.g. 422
|
|
37
|
+
* validation errors). Those are handled in getVideoUrl().
|
|
34
38
|
*/
|
|
35
39
|
function mapFalStatusToVideoStatus(
|
|
36
40
|
falStatus: FalQueueStatus,
|
|
@@ -114,9 +118,26 @@ export class FalVideoAdapter<TModel extends FalModel> extends BaseVideoAdapter<
|
|
|
114
118
|
}
|
|
115
119
|
|
|
116
120
|
async getVideoUrl(jobId: string): Promise<VideoUrlResult> {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
let result
|
|
122
|
+
try {
|
|
123
|
+
result = await fal.queue.result(this.model, {
|
|
124
|
+
requestId: jobId,
|
|
125
|
+
})
|
|
126
|
+
} catch (error: any) {
|
|
127
|
+
// fal.ai may report COMPLETED status but throw on result fetch
|
|
128
|
+
// (e.g. 422 validation errors). Extract the detailed error info.
|
|
129
|
+
const detail = error?.body?.detail
|
|
130
|
+
if (Array.isArray(detail)) {
|
|
131
|
+
const messages = detail.map(
|
|
132
|
+
(d: { msg?: string; loc?: Array<string> }) =>
|
|
133
|
+
d.loc ? `${d.loc.join('.')}: ${d.msg}` : d.msg,
|
|
134
|
+
)
|
|
135
|
+
throw new Error(`Video generation failed: ${messages.join('; ')}`)
|
|
136
|
+
}
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Failed to retrieve video result: ${error.message || error}`,
|
|
139
|
+
)
|
|
140
|
+
}
|
|
120
141
|
|
|
121
142
|
const data = result.data as FalVideoResultData
|
|
122
143
|
|