@mixio-pro/kalaasetu-mcp 1.0.14 → 1.0.16
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 +1 -1
- package/src/storage/gcs.ts +0 -3
- package/src/tools/gemini.ts +30 -21
- package/src/tools/image-to-video.sdk-backup.ts +100 -43
- package/src/utils/fal.utils.ts +15 -7
package/package.json
CHANGED
package/src/storage/gcs.ts
CHANGED
|
@@ -18,9 +18,6 @@ export class GCSStorageProvider implements StorageProvider {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
async init(): Promise<void> {
|
|
21
|
-
console.log(
|
|
22
|
-
`Initializing GCS Storage Provider with bucket: ${this.bucket}`
|
|
23
|
-
);
|
|
24
21
|
// Verify we can get credentials
|
|
25
22
|
try {
|
|
26
23
|
await this.auth.getClient();
|
package/src/tools/gemini.ts
CHANGED
|
@@ -167,18 +167,20 @@ async function processVideoInput(
|
|
|
167
167
|
} else {
|
|
168
168
|
// Local file processing - use File Upload API
|
|
169
169
|
const storage = getStorage();
|
|
170
|
-
|
|
170
|
+
|
|
171
171
|
// Check if file exists
|
|
172
172
|
const exists = await storage.exists(input);
|
|
173
173
|
if (!exists) {
|
|
174
174
|
// Try to provide more helpful error information
|
|
175
175
|
const isAbsolute = path.isAbsolute(input);
|
|
176
|
-
const resolvedPath = isAbsolute
|
|
176
|
+
const resolvedPath = isAbsolute
|
|
177
|
+
? input
|
|
178
|
+
: path.resolve(process.cwd(), input);
|
|
177
179
|
throw new Error(
|
|
178
180
|
`Video file not found: ${input}\n` +
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
181
|
+
`Resolved path: ${resolvedPath}\n` +
|
|
182
|
+
`Is absolute: ${isAbsolute}\n` +
|
|
183
|
+
`CWD: ${process.cwd()}`
|
|
182
184
|
);
|
|
183
185
|
}
|
|
184
186
|
|
|
@@ -192,7 +194,7 @@ async function processVideoInput(
|
|
|
192
194
|
export const geminiTextToImage = {
|
|
193
195
|
name: "generateImage",
|
|
194
196
|
description:
|
|
195
|
-
"Generate images from text prompts using Gemini image models with optional reference images",
|
|
197
|
+
"Generate images from text prompts using Gemini image models with optional reference images. Returns the URL of the generated image.",
|
|
196
198
|
parameters: z.object({
|
|
197
199
|
prompt: z.string().describe("Text description of the image to generate"),
|
|
198
200
|
aspect_ratio: z
|
|
@@ -202,7 +204,9 @@ export const geminiTextToImage = {
|
|
|
202
204
|
output_path: z
|
|
203
205
|
.string()
|
|
204
206
|
.optional()
|
|
205
|
-
.describe(
|
|
207
|
+
.describe(
|
|
208
|
+
"File path to save the generated image (optional, auto-generated if not provided)"
|
|
209
|
+
),
|
|
206
210
|
reference_images: z
|
|
207
211
|
.array(z.string())
|
|
208
212
|
.optional()
|
|
@@ -243,31 +247,35 @@ export const geminiTextToImage = {
|
|
|
243
247
|
textResponse += part.text;
|
|
244
248
|
} else if (part.inlineData?.data) {
|
|
245
249
|
const imageData = part.inlineData.data;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
250
|
+
// Always save the image - use provided path or generate one
|
|
251
|
+
const outputPath =
|
|
252
|
+
args.output_path ||
|
|
253
|
+
generateTimestampedFilename("generated_image.png");
|
|
254
|
+
const storage = getStorage();
|
|
255
|
+
const url = await storage.writeFile(
|
|
256
|
+
outputPath,
|
|
257
|
+
Buffer.from(imageData, "base64")
|
|
258
|
+
);
|
|
259
|
+
images.push({
|
|
260
|
+
url,
|
|
261
|
+
filename: outputPath,
|
|
262
|
+
mimeType: "image/png",
|
|
263
|
+
});
|
|
258
264
|
}
|
|
259
265
|
}
|
|
260
266
|
}
|
|
261
267
|
|
|
262
268
|
if (images.length > 0) {
|
|
269
|
+
// Return the URL directly for easy parsing
|
|
263
270
|
return JSON.stringify({
|
|
271
|
+
url: images?.[0]?.url,
|
|
264
272
|
images,
|
|
265
273
|
message: textResponse || "Image generated successfully",
|
|
266
274
|
});
|
|
267
275
|
}
|
|
268
276
|
|
|
269
277
|
return (
|
|
270
|
-
textResponse || "Image generation completed but no
|
|
278
|
+
textResponse || "Image generation completed but no image was produced"
|
|
271
279
|
);
|
|
272
280
|
} catch (error: any) {
|
|
273
281
|
throw new Error(`Image generation failed: ${error.message}`);
|
|
@@ -465,7 +473,8 @@ export const geminiSingleSpeakerTts = {
|
|
|
465
473
|
const audioBuffer = Buffer.from(data, "base64");
|
|
466
474
|
|
|
467
475
|
// Use provided output path or generate default with timestamp
|
|
468
|
-
const outputPath =
|
|
476
|
+
const outputPath =
|
|
477
|
+
args.output_path || generateTimestampedFilename("voice_output.wav");
|
|
469
478
|
|
|
470
479
|
const storage = getStorage();
|
|
471
480
|
const url = await storage.writeFile(outputPath, audioBuffer);
|
|
@@ -15,27 +15,67 @@ function fileToBase64(filePath: string): { data: string; mimeType: string } {
|
|
|
15
15
|
const data = Buffer.from(buf).toString("base64");
|
|
16
16
|
// Detect mime type from extension
|
|
17
17
|
const ext = path.extname(filePath).toLowerCase();
|
|
18
|
-
const mimeType =
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
const mimeType =
|
|
19
|
+
ext === ".jpg" || ext === ".jpeg"
|
|
20
|
+
? "image/jpeg"
|
|
21
|
+
: ext === ".png"
|
|
22
|
+
? "image/png"
|
|
23
|
+
: ext === ".webp"
|
|
24
|
+
? "image/webp"
|
|
25
|
+
: "image/png";
|
|
21
26
|
return { data, mimeType };
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
export const imageToVideo = {
|
|
25
30
|
name: "imageToVideo",
|
|
26
|
-
description:
|
|
31
|
+
description:
|
|
32
|
+
"Generate videos from an image as starting first frame using Vertex Veo models (predictLongRunning + fetchPredictOperation).",
|
|
27
33
|
parameters: z.object({
|
|
28
34
|
prompt: z.string().describe("Text description for the video"),
|
|
29
|
-
image_path: z
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
image_path: z
|
|
36
|
+
.string()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe("Path to source image for image-to-video generation"),
|
|
39
|
+
aspect_ratio: z
|
|
40
|
+
.string()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Video aspect ratio: '16:9' or '9:16' (default: '9:16')"),
|
|
43
|
+
duration_seconds: z
|
|
44
|
+
.number()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Video duration in seconds: 4, 6, or 8 (default: 6)"),
|
|
47
|
+
resolution: z
|
|
48
|
+
.string()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Video resolution: '720p' or '1080p' (default: '720p')"),
|
|
51
|
+
negative_prompt: z
|
|
52
|
+
.string()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe("Text describing what not to include in the video"),
|
|
55
|
+
person_generation: z
|
|
56
|
+
.string()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe(
|
|
59
|
+
"Controls generation of people: 'allow_adult' (default for image-to-video) or 'allow_all'"
|
|
60
|
+
),
|
|
61
|
+
reference_images: z
|
|
62
|
+
.array(z.string())
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("Additional image paths for reference (max 3)"),
|
|
65
|
+
output_path: z
|
|
66
|
+
.string()
|
|
67
|
+
.optional()
|
|
68
|
+
.describe(
|
|
69
|
+
"Output MP4 file path (if multiple predictions, index suffix is added)"
|
|
70
|
+
),
|
|
71
|
+
gemini_api_key: z
|
|
72
|
+
.string()
|
|
73
|
+
.optional()
|
|
74
|
+
.describe("Gemini API key (uses GEMINI_API_KEY env var if not provided)"),
|
|
75
|
+
model_id: z
|
|
76
|
+
.string()
|
|
77
|
+
.optional()
|
|
78
|
+
.describe("Model ID (default: veo-2.0-generate-001)"),
|
|
39
79
|
}),
|
|
40
80
|
execute: async (args: {
|
|
41
81
|
prompt: string;
|
|
@@ -52,47 +92,49 @@ export const imageToVideo = {
|
|
|
52
92
|
}) => {
|
|
53
93
|
const apiKey = args.gemini_api_key || process.env.GEMINI_API_KEY;
|
|
54
94
|
if (!apiKey) {
|
|
55
|
-
throw new Error(
|
|
95
|
+
throw new Error(
|
|
96
|
+
"Gemini API key is required. Set GEMINI_API_KEY environment variable or pass gemini_api_key parameter. Get one at https://aistudio.google.com/app/apikey"
|
|
97
|
+
);
|
|
56
98
|
}
|
|
57
99
|
|
|
58
100
|
const model = args.model_id || "veo-2.0-generate-001";
|
|
59
|
-
|
|
101
|
+
|
|
60
102
|
// Initialize Google GenAI client
|
|
61
103
|
const genai = new GoogleGenAI({ apiKey });
|
|
62
104
|
|
|
63
105
|
// Build config for video generation
|
|
64
106
|
const config: any = {};
|
|
65
|
-
|
|
107
|
+
|
|
66
108
|
if (args.duration_seconds !== undefined) {
|
|
67
109
|
config.duration_seconds = args.duration_seconds;
|
|
68
110
|
} else {
|
|
69
111
|
config.duration_seconds = 6; // default
|
|
70
112
|
}
|
|
71
|
-
|
|
113
|
+
|
|
72
114
|
if (args.aspect_ratio) {
|
|
73
115
|
config.aspect_ratio = args.aspect_ratio;
|
|
74
116
|
}
|
|
75
117
|
|
|
76
118
|
try {
|
|
77
119
|
// Start video generation operation
|
|
78
|
-
console.
|
|
120
|
+
console.error(`Starting video generation with model: ${model}`);
|
|
79
121
|
let operation = await genai.models.generateVideos({
|
|
80
122
|
model,
|
|
81
123
|
prompt: args.prompt,
|
|
82
124
|
config,
|
|
83
125
|
});
|
|
84
126
|
|
|
85
|
-
console.
|
|
86
|
-
|
|
127
|
+
console.error("Operation started, waiting for completion...");
|
|
128
|
+
|
|
87
129
|
// Poll until operation is complete (max 10 minutes)
|
|
88
130
|
let tries = 0;
|
|
89
131
|
const maxTries = 60; // 10 minutes with 10s intervals
|
|
90
|
-
|
|
132
|
+
|
|
91
133
|
while (!operation.done && tries < maxTries) {
|
|
92
134
|
await wait(10000); // Wait 10 seconds
|
|
93
135
|
tries++;
|
|
94
|
-
console.
|
|
95
|
-
|
|
136
|
+
console.error(`Polling attempt ${tries}/${maxTries}...`);
|
|
137
|
+
|
|
96
138
|
operation = await genai.operations.getVideosOperation({
|
|
97
139
|
operation: operation,
|
|
98
140
|
});
|
|
@@ -102,60 +144,75 @@ export const imageToVideo = {
|
|
|
102
144
|
throw new Error("Video generation timed out after 10 minutes");
|
|
103
145
|
}
|
|
104
146
|
|
|
105
|
-
console.
|
|
106
|
-
console.
|
|
147
|
+
console.error("Operation completed!");
|
|
148
|
+
console.error(
|
|
149
|
+
"Full Response:",
|
|
150
|
+
JSON.stringify(operation.response, null, 2)
|
|
151
|
+
);
|
|
107
152
|
|
|
108
153
|
// Extract generated videos from response
|
|
109
154
|
const generatedVideos = operation.response?.generatedVideos || [];
|
|
110
|
-
|
|
155
|
+
|
|
111
156
|
if (!generatedVideos || generatedVideos.length === 0) {
|
|
112
157
|
const respStr = JSON.stringify(operation.response, null, 2);
|
|
113
|
-
return `Video generation completed but no videos found in response.\n\nFull Response:\n${respStr.slice(
|
|
158
|
+
return `Video generation completed but no videos found in response.\n\nFull Response:\n${respStr.slice(
|
|
159
|
+
0,
|
|
160
|
+
2000
|
|
161
|
+
)}${respStr.length > 2000 ? "\n...(truncated)" : ""}`;
|
|
114
162
|
}
|
|
115
163
|
|
|
116
164
|
// Download and save videos
|
|
117
165
|
const outputs: string[] = [];
|
|
118
|
-
|
|
166
|
+
|
|
119
167
|
for (let i = 0; i < generatedVideos.length; i++) {
|
|
120
168
|
const generatedVideo = generatedVideos[i];
|
|
121
169
|
const videoUri = generatedVideo?.video?.uri;
|
|
122
|
-
|
|
170
|
+
|
|
123
171
|
if (!videoUri) {
|
|
124
172
|
console.warn(`Video ${i} has no URI`);
|
|
125
173
|
continue;
|
|
126
174
|
}
|
|
127
175
|
|
|
128
|
-
console.
|
|
129
|
-
|
|
176
|
+
console.error(
|
|
177
|
+
`Downloading video ${i + 1}/${generatedVideos.length}...`
|
|
178
|
+
);
|
|
179
|
+
|
|
130
180
|
// Download video from URI
|
|
131
181
|
const videoUrl = `${videoUri}&key=${apiKey}`;
|
|
132
182
|
const response = await fetch(videoUrl);
|
|
133
|
-
|
|
183
|
+
|
|
134
184
|
if (!response.ok) {
|
|
135
|
-
throw new Error(
|
|
185
|
+
throw new Error(
|
|
186
|
+
`Failed to download video: ${response.status} ${response.statusText}`
|
|
187
|
+
);
|
|
136
188
|
}
|
|
137
|
-
|
|
189
|
+
|
|
138
190
|
const buffer = await response.arrayBuffer();
|
|
139
|
-
|
|
191
|
+
|
|
140
192
|
// Save video to file
|
|
141
193
|
const filePath = args.output_path
|
|
142
|
-
?
|
|
143
|
-
|
|
194
|
+
? i === 0
|
|
195
|
+
? args.output_path
|
|
196
|
+
: args.output_path.replace(/\.mp4$/i, `_${i}.mp4`)
|
|
197
|
+
: `video_output_${Date.now()}${i === 0 ? "" : "_" + i}.mp4`;
|
|
144
198
|
const absPath = path.resolve(filePath);
|
|
145
|
-
|
|
199
|
+
|
|
146
200
|
fs.writeFileSync(absPath, Buffer.from(buffer));
|
|
147
201
|
outputs.push(absPath);
|
|
148
|
-
console.
|
|
202
|
+
console.error(`Saved video to: ${absPath}`);
|
|
149
203
|
}
|
|
150
204
|
|
|
151
205
|
if (outputs.length > 0) {
|
|
152
|
-
return `Video(s) saved successfully:\n${outputs
|
|
206
|
+
return `Video(s) saved successfully:\n${outputs
|
|
207
|
+
.map((p, i) => `${i + 1}. ${p}`)
|
|
208
|
+
.join("\n")}`;
|
|
153
209
|
}
|
|
154
210
|
|
|
155
211
|
return "Video generation completed but no videos were saved.";
|
|
156
|
-
|
|
157
212
|
} catch (error: any) {
|
|
158
|
-
throw new Error(
|
|
213
|
+
throw new Error(
|
|
214
|
+
`Video generation failed: ${error.message || JSON.stringify(error)}`
|
|
215
|
+
);
|
|
159
216
|
}
|
|
160
217
|
},
|
|
161
218
|
};
|
package/src/utils/fal.utils.ts
CHANGED
|
@@ -8,14 +8,16 @@ export async function callFalModel(
|
|
|
8
8
|
const { falKey, logs = true } = options;
|
|
9
9
|
const key = falKey || process.env.FAL_KEY;
|
|
10
10
|
if (!key) {
|
|
11
|
-
throw new Error(
|
|
11
|
+
throw new Error(
|
|
12
|
+
"FAL_KEY is required. Provide it via fal_key parameter or FAL_KEY environment variable."
|
|
13
|
+
);
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
fal.config({
|
|
15
17
|
credentials: key,
|
|
16
18
|
});
|
|
17
19
|
|
|
18
|
-
console.
|
|
20
|
+
console.error(`[${modelName}] Submitting request to FAL AI...`);
|
|
19
21
|
|
|
20
22
|
try {
|
|
21
23
|
const result = await fal.subscribe(modelName, {
|
|
@@ -23,23 +25,29 @@ export async function callFalModel(
|
|
|
23
25
|
logs,
|
|
24
26
|
onQueueUpdate: (update) => {
|
|
25
27
|
if (update.status === "IN_PROGRESS") {
|
|
26
|
-
console.
|
|
28
|
+
console.error(`[${modelName}] Status: ${update.status}`);
|
|
27
29
|
if (logs && "logs" in update && update.logs) {
|
|
28
30
|
update.logs.forEach((log) => {
|
|
29
|
-
console.
|
|
31
|
+
console.error(`[${modelName}] ${log.message}`);
|
|
30
32
|
});
|
|
31
33
|
}
|
|
32
34
|
} else if (update.status === "IN_QUEUE") {
|
|
33
|
-
console.
|
|
35
|
+
console.error(
|
|
36
|
+
`[${modelName}] Status: ${update.status} - Waiting in queue...`
|
|
37
|
+
);
|
|
34
38
|
}
|
|
35
39
|
},
|
|
36
40
|
});
|
|
37
41
|
|
|
38
|
-
console.
|
|
42
|
+
console.error(`[${modelName}] Generation completed successfully`);
|
|
39
43
|
|
|
40
44
|
return result;
|
|
41
45
|
} catch (error: any) {
|
|
42
46
|
console.error(`[${modelName}] Error:`, error);
|
|
43
|
-
throw new Error(
|
|
47
|
+
throw new Error(
|
|
48
|
+
`FAL AI ${modelName} generation failed: ${
|
|
49
|
+
error.message || JSON.stringify(error)
|
|
50
|
+
}`
|
|
51
|
+
);
|
|
44
52
|
}
|
|
45
53
|
}
|