@mixio-pro/kalaasetu-mcp 2.1.0 → 2.1.2-beta
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 +6 -2
- package/src/index.ts +6 -5
- package/src/storage/index.ts +4 -3
- package/src/tools/fal/config.ts +9 -8
- package/src/tools/fal/dynamic-tools.ts +214 -237
- package/src/tools/fal/models.ts +115 -93
- package/src/tools/fal/storage.ts +66 -61
- package/src/tools/gemini.ts +302 -281
- package/src/tools/get-status.ts +50 -46
- package/src/tools/image-to-video.ts +309 -300
- package/src/tools/perplexity.ts +188 -172
- package/src/tools/youtube.ts +45 -41
- package/src/utils/llm-prompt-enhancer.ts +3 -2
- package/src/utils/logger.ts +65 -0
- package/src/utils/openmeter.ts +123 -0
- package/src/utils/prompt-enhancer-presets.ts +7 -5
- package/src/utils/remote-sync.ts +19 -10
- package/src/utils/tool-credits.ts +104 -0
- package/src/utils/tool-wrapper.ts +37 -6
- package/src/utils/url-file.ts +4 -3
- package/src/test-context.ts +0 -52
- package/src/test-error-handling.ts +0 -31
- package/src/tools/image-to-video.sdk-backup.ts +0 -218
package/src/tools/fal/models.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
DEFAULT_TIMEOUT,
|
|
7
7
|
} from "./config";
|
|
8
8
|
import { safeToolExecute } from "../../utils/tool-wrapper";
|
|
9
|
+
import { logger } from "../../utils/logger";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Extract simplified input schema from FAL OpenAPI response.
|
|
@@ -20,7 +21,7 @@ function extractInputSchema(openApiSchema: any): Record<string, any> | null {
|
|
|
20
21
|
const inputSchemaKey = Object.keys(schemas).find(
|
|
21
22
|
(key) =>
|
|
22
23
|
key.toLowerCase().includes("input") &&
|
|
23
|
-
!key.toLowerCase().includes("output")
|
|
24
|
+
!key.toLowerCase().includes("output"),
|
|
24
25
|
);
|
|
25
26
|
|
|
26
27
|
if (!inputSchemaKey) return null;
|
|
@@ -31,7 +32,7 @@ function extractInputSchema(openApiSchema: any): Record<string, any> | null {
|
|
|
31
32
|
// Extract simplified properties
|
|
32
33
|
const simplified: Record<string, any> = {};
|
|
33
34
|
for (const [propName, propDef] of Object.entries(
|
|
34
|
-
inputSchema.properties as Record<string, any
|
|
35
|
+
inputSchema.properties as Record<string, any>,
|
|
35
36
|
)) {
|
|
36
37
|
simplified[propName] = {
|
|
37
38
|
type: propDef.type,
|
|
@@ -66,64 +67,81 @@ export const falListPresets = {
|
|
|
66
67
|
}),
|
|
67
68
|
timeoutMs: 60000, // Allow more time for schema fetching
|
|
68
69
|
execute: async (args: { refresh_schemas?: boolean }) => {
|
|
69
|
-
return safeToolExecute(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
70
|
+
return safeToolExecute(
|
|
71
|
+
async () => {
|
|
72
|
+
const config = loadFalConfig();
|
|
73
|
+
let updated = false;
|
|
74
|
+
|
|
75
|
+
// Fetch schemas for presets that don't have them (or if refresh requested)
|
|
76
|
+
for (const preset of config.presets) {
|
|
77
|
+
const hasSchema =
|
|
78
|
+
preset.input_schema && Object.keys(preset.input_schema).length > 0;
|
|
79
|
+
const shouldFetch = !hasSchema || args.refresh_schemas;
|
|
80
|
+
logger.debug(
|
|
81
|
+
`[fal_list_presets] ${
|
|
82
|
+
preset.presetName
|
|
83
|
+
}: shouldFetch=${shouldFetch}, hasSchema=${!!preset.input_schema}, refresh=${
|
|
84
|
+
args.refresh_schemas
|
|
85
|
+
}`,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
if (shouldFetch) {
|
|
89
|
+
try {
|
|
90
|
+
const url = `https://fal.ai/api/openapi/queue/openapi.json?endpoint_id=${preset.modelId}`;
|
|
91
|
+
logger.debug(`[fal_list_presets] Fetching schema from: ${url}`);
|
|
92
|
+
const response = await fetch(url, {
|
|
93
|
+
method: "GET",
|
|
94
|
+
signal: AbortSignal.timeout(10000),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (response.ok) {
|
|
98
|
+
const openApiSchema = await response.json();
|
|
99
|
+
const simplified = extractInputSchema(openApiSchema);
|
|
100
|
+
logger.debug(
|
|
101
|
+
`[fal_list_presets] Extracted schema for ${preset.presetName}:`,
|
|
102
|
+
simplified ? Object.keys(simplified) : null,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
if (simplified) {
|
|
106
|
+
preset.input_schema = simplified;
|
|
107
|
+
updated = true;
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
logger.warn(
|
|
111
|
+
`[fal_list_presets] Fetch failed: ${response.status}`,
|
|
112
|
+
);
|
|
97
113
|
}
|
|
98
|
-
}
|
|
99
|
-
|
|
114
|
+
} catch (e: any) {
|
|
115
|
+
logger.warn(
|
|
116
|
+
`[fal_list_presets] Error fetching schema for ${preset.presetName}:`,
|
|
117
|
+
e.message,
|
|
118
|
+
);
|
|
100
119
|
}
|
|
101
|
-
} catch (e: any) {
|
|
102
|
-
console.error(
|
|
103
|
-
`[fal_list_presets] Error fetching schema for ${preset.presetName}:`,
|
|
104
|
-
e.message
|
|
105
|
-
);
|
|
106
120
|
}
|
|
107
121
|
}
|
|
108
|
-
}
|
|
109
122
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const summary = config.presets.map((p) => ({
|
|
117
|
-
presetName: p.presetName,
|
|
118
|
-
intent: p.intent,
|
|
119
|
-
inputType: p.inputType,
|
|
120
|
-
outputType: p.outputType,
|
|
121
|
-
description: p.description,
|
|
122
|
-
inputSchema: p.input_schema,
|
|
123
|
-
}));
|
|
123
|
+
// Save updated config if schemas were fetched
|
|
124
|
+
if (updated) {
|
|
125
|
+
logger.info(`[fal_list_presets] Saving updated config...`);
|
|
126
|
+
const saved = saveFalConfig(config);
|
|
127
|
+
logger.info(`[fal_list_presets] Config saved: ${saved}`);
|
|
128
|
+
}
|
|
124
129
|
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
// Return enriched preset list
|
|
131
|
+
const summary = config.presets.map((p) => ({
|
|
132
|
+
presetName: p.presetName,
|
|
133
|
+
intent: p.intent,
|
|
134
|
+
inputType: p.inputType,
|
|
135
|
+
outputType: p.outputType,
|
|
136
|
+
description: p.description,
|
|
137
|
+
inputSchema: p.input_schema,
|
|
138
|
+
}));
|
|
139
|
+
|
|
140
|
+
return JSON.stringify(summary, null, 2);
|
|
141
|
+
},
|
|
142
|
+
"fal_list_presets",
|
|
143
|
+
{ toolName: "fal_list_presets" },
|
|
144
|
+
);
|
|
127
145
|
},
|
|
128
146
|
};
|
|
129
147
|
|
|
@@ -146,52 +164,56 @@ export const falGetPresetDetails = {
|
|
|
146
164
|
}),
|
|
147
165
|
timeoutMs: 30000,
|
|
148
166
|
execute: async (args: { preset_name: string }) => {
|
|
149
|
-
return safeToolExecute(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
167
|
+
return safeToolExecute(
|
|
168
|
+
async () => {
|
|
169
|
+
const config = loadFalConfig();
|
|
170
|
+
const preset = config.presets.find(
|
|
171
|
+
(p) => p.presetName === args.preset_name,
|
|
172
|
+
);
|
|
173
|
+
if (!preset) {
|
|
174
|
+
throw new Error(`Preset '${args.preset_name}' not found.`);
|
|
175
|
+
}
|
|
157
176
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
177
|
+
// Fetch live model metadata/schema from fal.ai API
|
|
178
|
+
// Based on: https://fal.ai/api/openapi/queue/openapi.json?endpoint_id={model_id}
|
|
179
|
+
let modelMetadata: any = null;
|
|
180
|
+
let schemaSource = "none";
|
|
162
181
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
182
|
+
try {
|
|
183
|
+
const url = `https://fal.ai/api/openapi/queue/openapi.json?endpoint_id=${preset.modelId}`;
|
|
184
|
+
const schema = await publicRequest(url);
|
|
166
185
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
186
|
+
// Extract relevant schema parts if possible, or return full schema
|
|
187
|
+
if (schema) {
|
|
188
|
+
modelMetadata = schema;
|
|
189
|
+
schemaSource = "api";
|
|
190
|
+
}
|
|
191
|
+
} catch (e) {
|
|
192
|
+
// console.error(`Failed to fetch schema for ${preset.modelId}:`, e);
|
|
193
|
+
// Fallback to locally defined schema if available
|
|
171
194
|
}
|
|
172
|
-
} catch (e) {
|
|
173
|
-
// console.error(`Failed to fetch schema for ${preset.modelId}:`, e);
|
|
174
|
-
// Fallback to locally defined schema if available
|
|
175
|
-
}
|
|
176
195
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
196
|
+
// Fallback: If API failed or returned nothing, use manual schema from config
|
|
197
|
+
if (!modelMetadata && preset.input_schema) {
|
|
198
|
+
modelMetadata = preset.input_schema;
|
|
199
|
+
schemaSource = "local_config";
|
|
200
|
+
}
|
|
182
201
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
202
|
+
return JSON.stringify(
|
|
203
|
+
{
|
|
204
|
+
preset,
|
|
205
|
+
modelMetadata,
|
|
206
|
+
_meta: {
|
|
207
|
+
schemaSource,
|
|
208
|
+
},
|
|
189
209
|
},
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
210
|
+
null,
|
|
211
|
+
2,
|
|
212
|
+
);
|
|
213
|
+
},
|
|
214
|
+
"fal_get_preset_details",
|
|
215
|
+
{ toolName: "fal_get_preset_details" },
|
|
216
|
+
);
|
|
195
217
|
},
|
|
196
218
|
};
|
|
197
219
|
|
package/src/tools/fal/storage.ts
CHANGED
|
@@ -8,6 +8,7 @@ import * as fs from "fs";
|
|
|
8
8
|
import { safeToolExecute } from "../../utils/tool-wrapper";
|
|
9
9
|
import * as path from "path";
|
|
10
10
|
import { FAL_REST_URL, AUTHENTICATED_TIMEOUT, getApiKey } from "./config";
|
|
11
|
+
import { logger } from "../../utils/logger";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Get MIME type from file extension.
|
|
@@ -46,82 +47,86 @@ export const falUploadFile = {
|
|
|
46
47
|
path: z
|
|
47
48
|
.string()
|
|
48
49
|
.describe(
|
|
49
|
-
"The absolute local path to the file to upload (e.g., '/Users/name/images/input.jpg')."
|
|
50
|
+
"The absolute local path to the file to upload (e.g., '/Users/name/images/input.jpg').",
|
|
50
51
|
),
|
|
51
52
|
}),
|
|
52
53
|
timeoutMs: 300000,
|
|
53
54
|
execute: async (args: { path: string }) => {
|
|
54
|
-
return safeToolExecute(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
return safeToolExecute(
|
|
56
|
+
async () => {
|
|
57
|
+
// Validate file exists
|
|
58
|
+
if (!fs.existsSync(args.path)) {
|
|
59
|
+
throw new Error(`File not found: ${args.path}`);
|
|
60
|
+
}
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
const filename = path.basename(args.path);
|
|
63
|
+
const fileSize = fs.statSync(args.path).size;
|
|
64
|
+
const contentType = getMimeType(args.path);
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
// Step 1: Initiate upload
|
|
67
|
+
const initiateUrl = `${FAL_REST_URL}/storage/upload/initiate?storage_type=fal-cdn-v3`;
|
|
68
|
+
const initiatePayload = {
|
|
69
|
+
content_type: contentType,
|
|
70
|
+
file_name: filename,
|
|
71
|
+
};
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
logger.debug(`[fal_upload] Initiating upload for ${filename}...`);
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
const initiateResponse = await fetch(initiateUrl, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: {
|
|
78
|
+
Authorization: `Key ${getApiKey()}`,
|
|
79
|
+
"Content-Type": "application/json",
|
|
80
|
+
},
|
|
81
|
+
body: JSON.stringify(initiatePayload),
|
|
82
|
+
signal: AbortSignal.timeout(AUTHENTICATED_TIMEOUT),
|
|
83
|
+
});
|
|
82
84
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
if (!initiateResponse.ok) {
|
|
86
|
+
const errorText = await initiateResponse.text();
|
|
87
|
+
throw new Error(
|
|
88
|
+
`[${initiateResponse.status}] Failed to initiate upload: ${errorText}`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
89
91
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
const initiateData = (await initiateResponse.json()) as {
|
|
93
|
+
file_url: string;
|
|
94
|
+
upload_url: string;
|
|
95
|
+
};
|
|
96
|
+
const { file_url, upload_url } = initiateData;
|
|
95
97
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
// Step 2: Upload file content
|
|
99
|
+
logger.debug(`[fal_upload] Uploading file content...`);
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
const fileContent = fs.readFileSync(args.path);
|
|
100
102
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
103
|
+
const uploadResponse = await fetch(upload_url, {
|
|
104
|
+
method: "PUT",
|
|
105
|
+
headers: {
|
|
106
|
+
"Content-Type": contentType,
|
|
107
|
+
},
|
|
108
|
+
body: fileContent,
|
|
109
|
+
signal: AbortSignal.timeout(AUTHENTICATED_TIMEOUT),
|
|
110
|
+
});
|
|
109
111
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
if (!uploadResponse.ok) {
|
|
113
|
+
const errorText = await uploadResponse.text();
|
|
114
|
+
throw new Error(
|
|
115
|
+
`[${uploadResponse.status}] Failed to upload file: ${errorText}`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
116
118
|
|
|
117
|
-
|
|
119
|
+
logger.info(`[fal_upload] Upload completed successfully`);
|
|
118
120
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
return JSON.stringify({
|
|
122
|
+
file_url,
|
|
123
|
+
file_name: filename,
|
|
124
|
+
file_size: fileSize,
|
|
125
|
+
content_type: contentType,
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
"fal_upload_file",
|
|
129
|
+
{ toolName: "fal_upload_file" },
|
|
130
|
+
);
|
|
126
131
|
},
|
|
127
132
|
};
|