@mixio-pro/kalaasetu-mcp 1.1.3 → 1.2.0

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": "@mixio-pro/kalaasetu-mcp",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "description": "A powerful Model Context Protocol server providing AI tools for content generation and analysis",
5
5
  "type": "module",
6
6
  "module": "src/index.ts",
package/src/index.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env bun
2
2
  import { FastMCP } from "fastmcp";
3
+ import pkg from "../package.json";
3
4
  import { geminiEditImage, geminiTextToImage } from "./tools/gemini";
4
5
  import { imageToVideo } from "./tools/image-to-video";
5
6
  import {
6
- falListModels,
7
- falSearchModels,
8
- falGetSchema,
9
- falGenerateContent,
7
+ falListPresets,
8
+ falGetPresetDetails,
9
+ falGetConfig,
10
+ falGenerate,
10
11
  falGetResult,
11
12
  falGetStatus,
12
13
  falCancelRequest,
@@ -15,7 +16,7 @@ import {
15
16
 
16
17
  const server = new FastMCP({
17
18
  name: "Kalaasetu MCP Server",
18
- version: "1.1.0",
19
+ version: pkg.version as any,
19
20
  });
20
21
 
21
22
  // Gemini Image Tools
@@ -40,10 +41,10 @@ server.addTool(imageToVideo);
40
41
  // server.addTool(perplexityVideos);
41
42
 
42
43
  // Fal AI Tools
43
- server.addTool(falListModels);
44
- server.addTool(falSearchModels);
45
- server.addTool(falGetSchema);
46
- server.addTool(falGenerateContent);
44
+ server.addTool(falGetConfig);
45
+ server.addTool(falListPresets);
46
+ server.addTool(falGetPresetDetails);
47
+ server.addTool(falGenerate);
47
48
  server.addTool(falGetResult);
48
49
  server.addTool(falGetStatus);
49
50
  server.addTool(falCancelRequest);
@@ -0,0 +1,52 @@
1
+ import { z } from "zod";
2
+ import { safeToolExecute } from "./utils/tool-wrapper";
3
+
4
+ async function runTest() {
5
+ console.log("Running Error Context Verification Test...");
6
+
7
+ // 1. Mock Schema Validation Error
8
+ const schema = z.object({
9
+ prompt: z.string(),
10
+ count: z.number().min(1).max(5),
11
+ });
12
+
13
+ const mockToolWithValidation = async (args: any) => {
14
+ // Simulate what happens in a real tool when Zod parsing fails inside the execute block
15
+ // OR if we manually parse and it fails
16
+ // Most tools currently don't re-parse inside execute because MCP handles it?
17
+ // Wait, the tools define `parameters` schema, but the `execute` function receives `args`.
18
+ // The MCP server framework usually does the validation BEFORE calling execute.
19
+ // BUT if we look at the code, some tools do manual checks or parsing.
20
+
21
+ // Let's simulate a manual validation failure inside execute, or a deep validation failure
22
+ try {
23
+ schema.parse(args);
24
+ } catch (error) {
25
+ throw error;
26
+ }
27
+ return "success";
28
+ };
29
+
30
+ const zodResult = await safeToolExecute(
31
+ async () => mockToolWithValidation({ prompt: 123, count: 10 }),
32
+ "ZodTool"
33
+ );
34
+ console.log("\n--- Zod Error Result ---");
35
+ console.log(JSON.stringify(zodResult, null, 2));
36
+
37
+ // 2. Mock API Error with Status
38
+ const mockToolWithApiError = async () => {
39
+ // Simulate a fetch error structure
40
+ const err: any = new Error("API request failed");
41
+ err.status = 429;
42
+ err.statusText = "Too Many Requests";
43
+ err.responseBody = "Rate limit exceeded. Retry in 60s.";
44
+ throw err;
45
+ };
46
+
47
+ const apiResult = await safeToolExecute(mockToolWithApiError, "ApiTool");
48
+ console.log("\n--- API Error Result ---");
49
+ console.log(JSON.stringify(apiResult, null, 2));
50
+ }
51
+
52
+ runTest();
@@ -0,0 +1,31 @@
1
+ import { safeToolExecute } from "./utils/tool-wrapper";
2
+
3
+ async function runTest() {
4
+ console.log("Running Error Handling Verification Test...");
5
+
6
+ // Mock tool that throws an error
7
+ const mockTool = async () => {
8
+ throw new Error("Simulated tool failure");
9
+ };
10
+
11
+ const result = await safeToolExecute(mockTool, "MockTool");
12
+
13
+ console.log("Result:", JSON.stringify(result, null, 2));
14
+
15
+ if (
16
+ result.isError === true &&
17
+ result.content &&
18
+ result.content[0]?.text.includes(
19
+ "Tool execution failed in MockTool: Simulated tool failure"
20
+ )
21
+ ) {
22
+ console.log(
23
+ "✅ Verification PASSED: Error was correctly caught and formatted."
24
+ );
25
+ } else {
26
+ console.error("❌ Verification FAILED: Unexpected result format.");
27
+ process.exit(1);
28
+ }
29
+ }
30
+
31
+ runTest();
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Configuration module for the fal.ai MCP server.
3
2
  * Provides centralized configuration settings for API endpoints, timeouts, and server metadata.
4
3
  */
4
+ import * as fs from "fs";
5
+ import * as path from "path";
5
6
 
6
7
  // API URLs
7
8
  export const FAL_BASE_URL = "https://fal.ai/api";
@@ -32,3 +33,96 @@ export function getApiKey(): string {
32
33
  }
33
34
  return apiKey;
34
35
  }
36
+
37
+ /**
38
+ * Interface for a FAL preset configuration.
39
+ */
40
+ export interface FalPresetConfig {
41
+ presetName: string;
42
+ intent: string;
43
+ modelId: string;
44
+ description?: string;
45
+ defaultParams?: Record<string, any>;
46
+ inputType?: "text" | "image" | "video" | "audio" | "custom";
47
+ outputType?: "image" | "video" | "audio" | "text" | "custom";
48
+ }
49
+
50
+ /**
51
+ * Interface for the FAL configuration file.
52
+ */
53
+ export interface FalConfig {
54
+ presets: FalPresetConfig[];
55
+ }
56
+
57
+ /**
58
+ * Default fallback presets in case no config file is provided.
59
+ */
60
+ export const DEFAULT_PRESETS: FalPresetConfig[] = [
61
+ {
62
+ presetName: "cinematic_image",
63
+ intent: "Generate high-quality cinematic images from text prompts",
64
+ modelId: "fal-ai/flux/pro/v1.1",
65
+ description: "FLUX.1 [pro] v1.1 for state-of-the-art image generation",
66
+ defaultParams: {
67
+ image_size: "landscape_4_3",
68
+ },
69
+ inputType: "text",
70
+ outputType: "image",
71
+ },
72
+ {
73
+ presetName: "cinematic_video",
74
+ intent: "Generate realistic cinematic video from text or image",
75
+ modelId: "fal-ai/luma-dream-machine",
76
+ description: "Luma Dream Machine for high-quality video generation",
77
+ inputType: "custom",
78
+ outputType: "video",
79
+ },
80
+ {
81
+ presetName: "creative_image",
82
+ intent: "Generate creative or artistic images with high flexibility",
83
+ modelId: "fal-ai/stable-diffusion-v35-large",
84
+ description: "Stable Diffusion 3.5 Large for diverse image styles",
85
+ inputType: "text",
86
+ outputType: "image",
87
+ },
88
+ ];
89
+
90
+ /**
91
+ * Load the FAL configuration from a JSON file.
92
+ * Defaults to DEFAULT_PRESETS if no path is provided or if the file cannot be read.
93
+ */
94
+ export function loadFalConfig(): FalConfig {
95
+ const configPath = process.env.FAL_CONFIG_JSON_PATH;
96
+
97
+ if (!configPath) {
98
+ console.error(
99
+ "FAL_CONFIG_JSON_PATH not set. Using internal default presets."
100
+ );
101
+ return { presets: DEFAULT_PRESETS };
102
+ }
103
+
104
+ try {
105
+ const absolutePath = path.isAbsolute(configPath)
106
+ ? configPath
107
+ : path.join(process.cwd(), configPath);
108
+
109
+ if (!fs.existsSync(absolutePath)) {
110
+ console.error(
111
+ `FAL config file not found at ${absolutePath}. Using default presets.`
112
+ );
113
+ return { presets: DEFAULT_PRESETS };
114
+ }
115
+
116
+ const fileContent = fs.readFileSync(absolutePath, "utf-8");
117
+ const config = JSON.parse(fileContent) as FalConfig;
118
+
119
+ if (!config.presets || !Array.isArray(config.presets)) {
120
+ throw new Error("Invalid FAL config: 'presets' must be an array.");
121
+ }
122
+
123
+ return config;
124
+ } catch (error: any) {
125
+ console.error(`Error loading FAL config: ${error.message}`);
126
+ return { presets: DEFAULT_PRESETS };
127
+ }
128
+ }
@@ -4,11 +4,14 @@
4
4
  */
5
5
 
6
6
  import { z } from "zod";
7
+ import { safeToolExecute } from "../../utils/tool-wrapper";
7
8
  import {
8
9
  FAL_QUEUE_URL,
9
10
  FAL_DIRECT_URL,
10
11
  AUTHENTICATED_TIMEOUT,
11
12
  getApiKey,
13
+ loadFalConfig,
14
+ type FalPresetConfig,
12
15
  } from "./config";
13
16
 
14
17
  /**
@@ -56,17 +59,20 @@ function sanitizeParameters(
56
59
  }
57
60
 
58
61
  /**
59
- * Generate content using a fal.ai model.
62
+ * Unified generation tool using presets defined in configuration.
60
63
  */
61
- export const falGenerateContent = {
62
- name: "fal_generate_content",
64
+ export const falGenerate = {
65
+ name: "fal_generate",
63
66
  description:
64
- "Generate content using any fal.ai model. Supports both direct execution and queued execution for long-running tasks.",
67
+ "Generate content using a named preset and optional parameters. Use fal_list_presets to discover available intents and preset names.",
65
68
  parameters: z.object({
66
- model: z.string().describe('The model ID to use (e.g., "fal-ai/flux/dev")'),
69
+ preset_name: z
70
+ .string()
71
+ .describe("The name of the preset to use (e.g., 'cinematic_image')"),
67
72
  parameters: z
68
73
  .record(z.string(), z.any())
69
- .describe("Model-specific parameters as a dictionary"),
74
+ .optional()
75
+ .describe("Optional model-specific parameters to override defaults"),
70
76
  queue: z
71
77
  .boolean()
72
78
  .optional()
@@ -75,23 +81,43 @@ export const falGenerateContent = {
75
81
  "Whether to use the queuing system for long-running tasks. Default: false"
76
82
  ),
77
83
  }),
84
+ timeoutMs: 300000,
78
85
  execute: async (args: {
79
- model: string;
80
- parameters: Record<string, any>;
86
+ preset_name: string;
87
+ parameters?: Record<string, any>;
81
88
  queue?: boolean;
82
89
  }) => {
83
- const sanitizedParams = sanitizeParameters(args.parameters);
90
+ return safeToolExecute(async () => {
91
+ const config = loadFalConfig();
92
+ const preset = config.presets.find(
93
+ (p) => p.presetName === args.preset_name
94
+ );
84
95
 
85
- const baseUrl = args.queue ? FAL_QUEUE_URL : FAL_DIRECT_URL;
86
- const url = `${baseUrl}/${args.model}`;
96
+ if (!preset) {
97
+ throw new Error(
98
+ `Preset '${args.preset_name}' not found. Use fal_list_presets to see available options.`
99
+ );
100
+ }
87
101
 
88
- console.error(`[fal_generate] Submitting request to ${url}...`);
102
+ // Merge defaults from config with runtime overrides
103
+ const mergedParams = {
104
+ ...(preset.defaultParams || {}),
105
+ ...(args.parameters || {}),
106
+ };
89
107
 
90
- const result = await authenticatedRequest(url, "POST", sanitizedParams);
108
+ const sanitizedParams = sanitizeParameters(mergedParams);
91
109
 
92
- console.error(`[fal_generate] Request completed successfully`);
110
+ const baseUrl = args.queue ? FAL_QUEUE_URL : FAL_DIRECT_URL;
111
+ const url = `${baseUrl}/${preset.modelId}`;
93
112
 
94
- return JSON.stringify(result);
113
+ console.error(
114
+ `[fal_generate] Using preset '${args.preset_name}' on model ${preset.modelId}...`
115
+ );
116
+
117
+ const result = await authenticatedRequest(url, "POST", sanitizedParams);
118
+
119
+ return JSON.stringify(result);
120
+ }, "fal_generate");
95
121
  },
96
122
  };
97
123
 
@@ -104,6 +130,7 @@ export const falGetResult = {
104
130
  parameters: z.object({
105
131
  url: z.string().describe("The response_url from a queued request"),
106
132
  }),
133
+ timeoutMs: 300000,
107
134
  execute: async (args: { url: string }) => {
108
135
  const result = await authenticatedRequest(args.url, "GET");
109
136
  return JSON.stringify(result);
@@ -119,6 +146,7 @@ export const falGetStatus = {
119
146
  parameters: z.object({
120
147
  url: z.string().describe("The status_url from a queued request"),
121
148
  }),
149
+ timeoutMs: 300000,
122
150
  execute: async (args: { url: string }) => {
123
151
  const result = await authenticatedRequest(args.url, "GET");
124
152
  return JSON.stringify(result);
@@ -134,8 +162,11 @@ export const falCancelRequest = {
134
162
  parameters: z.object({
135
163
  url: z.string().describe("The cancel_url from a queued request"),
136
164
  }),
165
+ timeoutMs: 300000,
137
166
  execute: async (args: { url: string }) => {
138
- const result = await authenticatedRequest(args.url, "PUT");
139
- return JSON.stringify(result);
167
+ return safeToolExecute(async () => {
168
+ const result = await authenticatedRequest(args.url, "PUT");
169
+ return JSON.stringify(result);
170
+ }, "fal_cancel_request");
140
171
  },
141
172
  };
@@ -3,9 +3,9 @@
3
3
  * Export all fal.ai tools for registration with the MCP server.
4
4
  */
5
5
 
6
- export { falListModels, falSearchModels, falGetSchema } from "./models";
6
+ export { falListPresets, falGetPresetDetails, falGetConfig } from "./models";
7
7
  export {
8
- falGenerateContent,
8
+ falGenerate,
9
9
  falGetResult,
10
10
  falGetStatus,
11
11
  falCancelRequest,
@@ -1,13 +1,63 @@
1
1
  /**
2
2
  * Models module for fal.ai MCP server.
3
- * Provides tools for listing, searching, and retrieving schemas for fal.ai models.
3
+ * Provides tools for retrieving information about configured fal.ai models.
4
4
  */
5
5
 
6
6
  import { z } from "zod";
7
- import { FAL_BASE_URL, DEFAULT_TIMEOUT } from "./config";
7
+ import { loadFalConfig, FAL_BASE_URL, DEFAULT_TIMEOUT } from "./config";
8
+ import { safeToolExecute } from "../../utils/tool-wrapper";
8
9
 
9
10
  /**
10
- * Make a non-authenticated request to fal.ai API.
11
+ * Tool to list available generation presets and their intents.
12
+ */
13
+ export const falListPresets = {
14
+ name: "fal_list_presets",
15
+ description:
16
+ "List all available generation presets, including their intents, input types, and output types. Use this to find the right preset for a task.",
17
+ parameters: z.object({}),
18
+ timeoutMs: 30000,
19
+ execute: async () => {
20
+ return safeToolExecute(async () => {
21
+ const config = loadFalConfig();
22
+ const summary = config.presets.map((p) => ({
23
+ presetName: p.presetName,
24
+ intent: p.intent,
25
+ inputType: p.inputType,
26
+ outputType: p.outputType,
27
+ description: p.description,
28
+ }));
29
+ return JSON.stringify(summary, null, 2);
30
+ }, "fal_list_presets");
31
+ },
32
+ };
33
+
34
+ /**
35
+ * Tool to get full details for a specific preset, including default parameters.
36
+ */
37
+ export const falGetPresetDetails = {
38
+ name: "fal_get_preset_details",
39
+ description:
40
+ "Get full details for a specific generation preset, including its model ID and default parameters.",
41
+ parameters: z.object({
42
+ preset_name: z.string().describe("The name of the preset to inspect"),
43
+ }),
44
+ timeoutMs: 30000,
45
+ execute: async (args: { preset_name: string }) => {
46
+ return safeToolExecute(async () => {
47
+ const config = loadFalConfig();
48
+ const preset = config.presets.find(
49
+ (p) => p.presetName === args.preset_name
50
+ );
51
+ if (!preset) {
52
+ throw new Error(`Preset '${args.preset_name}' not found.`);
53
+ }
54
+ return JSON.stringify(preset, null, 2);
55
+ }, "fal_get_preset_details");
56
+ },
57
+ };
58
+
59
+ /**
60
+ * Helper for making public API requests to fal.ai
11
61
  */
12
62
  async function publicRequest(url: string): Promise<any> {
13
63
  const response = await fetch(url, {
@@ -24,8 +74,24 @@ async function publicRequest(url: string): Promise<any> {
24
74
  }
25
75
 
26
76
  /**
27
- * List available models on fal.ai with optional pagination.
77
+ * Tool to retrieve the current full FAL configuration.
28
78
  */
79
+ export const falGetConfig = {
80
+ name: "fal_get_config",
81
+ description: "Retrieve the full FAL configuration JSON file content.",
82
+ parameters: z.object({}),
83
+ timeoutMs: 30000,
84
+ execute: async () => {
85
+ return safeToolExecute(async () => {
86
+ const config = loadFalConfig();
87
+ return JSON.stringify(config, null, 2);
88
+ }, "fal_get_config");
89
+ },
90
+ };
91
+
92
+ /*
93
+ // ORIGINAL UNRESTRICTED TOOLS - Commented out for reference
94
+
29
95
  export const falListModels = {
30
96
  name: "fal_list_models",
31
97
  description:
@@ -40,6 +106,7 @@ export const falListModels = {
40
106
  .optional()
41
107
  .describe("The total number of models to retrieve per page"),
42
108
  }),
109
+ timeoutMs: 300000,
43
110
  execute: async (args: { page?: number; total?: number }) => {
44
111
  let url = `${FAL_BASE_URL}/models`;
45
112
 
@@ -56,15 +123,13 @@ export const falListModels = {
56
123
  },
57
124
  };
58
125
 
59
- /**
60
- * Search for models on fal.ai based on keywords.
61
- */
62
126
  export const falSearchModels = {
63
127
  name: "fal_search_models",
64
128
  description: "Search for models on fal.ai based on keywords.",
65
129
  parameters: z.object({
66
130
  keywords: z.string().describe("The search terms to find models"),
67
131
  }),
132
+ timeoutMs: 300000,
68
133
  execute: async (args: { keywords: string }) => {
69
134
  const url = `${FAL_BASE_URL}/models?keywords=${encodeURIComponent(
70
135
  args.keywords
@@ -73,23 +138,4 @@ export const falSearchModels = {
73
138
  return JSON.stringify(result);
74
139
  },
75
140
  };
76
-
77
- /**
78
- * Get the OpenAPI schema for a specific model.
79
- */
80
- export const falGetSchema = {
81
- name: "fal_get_schema",
82
- description: "Get the OpenAPI schema for a specific fal.ai model.",
83
- parameters: z.object({
84
- model_id: z
85
- .string()
86
- .describe('The ID of the model (e.g., "fal-ai/flux/dev")'),
87
- }),
88
- execute: async (args: { model_id: string }) => {
89
- const url = `${FAL_BASE_URL}/openapi/queue/openapi.json?endpoint_id=${encodeURIComponent(
90
- args.model_id
91
- )}`;
92
- const result = await publicRequest(url);
93
- return JSON.stringify(result);
94
- },
95
- };
141
+ */
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { z } from "zod";
7
7
  import * as fs from "fs";
8
+ import { safeToolExecute } from "../../utils/tool-wrapper";
8
9
  import * as path from "path";
9
10
  import { FAL_REST_URL, AUTHENTICATED_TIMEOUT, getApiKey } from "./config";
10
11
 
@@ -41,76 +42,79 @@ export const falUploadFile = {
41
42
  parameters: z.object({
42
43
  path: z.string().describe("The absolute path to the file to upload"),
43
44
  }),
45
+ timeoutMs: 300000,
44
46
  execute: async (args: { path: string }) => {
45
- // Validate file exists
46
- if (!fs.existsSync(args.path)) {
47
- throw new Error(`File not found: ${args.path}`);
48
- }
47
+ return safeToolExecute(async () => {
48
+ // Validate file exists
49
+ if (!fs.existsSync(args.path)) {
50
+ throw new Error(`File not found: ${args.path}`);
51
+ }
49
52
 
50
- const filename = path.basename(args.path);
51
- const fileSize = fs.statSync(args.path).size;
52
- const contentType = getMimeType(args.path);
53
+ const filename = path.basename(args.path);
54
+ const fileSize = fs.statSync(args.path).size;
55
+ const contentType = getMimeType(args.path);
53
56
 
54
- // Step 1: Initiate upload
55
- const initiateUrl = `${FAL_REST_URL}/storage/upload/initiate?storage_type=fal-cdn-v3`;
56
- const initiatePayload = {
57
- content_type: contentType,
58
- file_name: filename,
59
- };
57
+ // Step 1: Initiate upload
58
+ const initiateUrl = `${FAL_REST_URL}/storage/upload/initiate?storage_type=fal-cdn-v3`;
59
+ const initiatePayload = {
60
+ content_type: contentType,
61
+ file_name: filename,
62
+ };
60
63
 
61
- console.error(`[fal_upload] Initiating upload for ${filename}...`);
64
+ console.error(`[fal_upload] Initiating upload for ${filename}...`);
62
65
 
63
- const initiateResponse = await fetch(initiateUrl, {
64
- method: "POST",
65
- headers: {
66
- Authorization: `Key ${getApiKey()}`,
67
- "Content-Type": "application/json",
68
- },
69
- body: JSON.stringify(initiatePayload),
70
- signal: AbortSignal.timeout(AUTHENTICATED_TIMEOUT),
71
- });
66
+ const initiateResponse = await fetch(initiateUrl, {
67
+ method: "POST",
68
+ headers: {
69
+ Authorization: `Key ${getApiKey()}`,
70
+ "Content-Type": "application/json",
71
+ },
72
+ body: JSON.stringify(initiatePayload),
73
+ signal: AbortSignal.timeout(AUTHENTICATED_TIMEOUT),
74
+ });
72
75
 
73
- if (!initiateResponse.ok) {
74
- const errorText = await initiateResponse.text();
75
- throw new Error(
76
- `[${initiateResponse.status}] Failed to initiate upload: ${errorText}`
77
- );
78
- }
76
+ if (!initiateResponse.ok) {
77
+ const errorText = await initiateResponse.text();
78
+ throw new Error(
79
+ `[${initiateResponse.status}] Failed to initiate upload: ${errorText}`
80
+ );
81
+ }
79
82
 
80
- const initiateData = (await initiateResponse.json()) as {
81
- file_url: string;
82
- upload_url: string;
83
- };
84
- const { file_url, upload_url } = initiateData;
83
+ const initiateData = (await initiateResponse.json()) as {
84
+ file_url: string;
85
+ upload_url: string;
86
+ };
87
+ const { file_url, upload_url } = initiateData;
85
88
 
86
- // Step 2: Upload file content
87
- console.error(`[fal_upload] Uploading file content...`);
89
+ // Step 2: Upload file content
90
+ console.error(`[fal_upload] Uploading file content...`);
88
91
 
89
- const fileContent = fs.readFileSync(args.path);
92
+ const fileContent = fs.readFileSync(args.path);
90
93
 
91
- const uploadResponse = await fetch(upload_url, {
92
- method: "PUT",
93
- headers: {
94
- "Content-Type": contentType,
95
- },
96
- body: fileContent,
97
- signal: AbortSignal.timeout(AUTHENTICATED_TIMEOUT),
98
- });
94
+ const uploadResponse = await fetch(upload_url, {
95
+ method: "PUT",
96
+ headers: {
97
+ "Content-Type": contentType,
98
+ },
99
+ body: fileContent,
100
+ signal: AbortSignal.timeout(AUTHENTICATED_TIMEOUT),
101
+ });
99
102
 
100
- if (!uploadResponse.ok) {
101
- const errorText = await uploadResponse.text();
102
- throw new Error(
103
- `[${uploadResponse.status}] Failed to upload file: ${errorText}`
104
- );
105
- }
103
+ if (!uploadResponse.ok) {
104
+ const errorText = await uploadResponse.text();
105
+ throw new Error(
106
+ `[${uploadResponse.status}] Failed to upload file: ${errorText}`
107
+ );
108
+ }
106
109
 
107
- console.error(`[fal_upload] Upload completed successfully`);
110
+ console.error(`[fal_upload] Upload completed successfully`);
108
111
 
109
- return JSON.stringify({
110
- file_url,
111
- file_name: filename,
112
- file_size: fileSize,
113
- content_type: contentType,
114
- });
112
+ return JSON.stringify({
113
+ file_url,
114
+ file_name: filename,
115
+ file_size: fileSize,
116
+ content_type: contentType,
117
+ });
118
+ }, "fal_upload_file");
115
119
  },
116
120
  };