@mixio-pro/kalaasetu-mcp 1.0.8 → 1.0.10
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/tools/gemini.ts +35 -13
- package/src/tools/image-to-video.ts +5 -2
- package/src/utils/filename.ts +22 -0
package/package.json
CHANGED
package/src/tools/gemini.ts
CHANGED
|
@@ -10,6 +10,7 @@ import * as os from "os";
|
|
|
10
10
|
import * as wav from "wav";
|
|
11
11
|
import { PassThrough } from "stream";
|
|
12
12
|
import { getStorage } from "../storage";
|
|
13
|
+
import { generateTimestampedFilename } from "../utils/filename";
|
|
13
14
|
|
|
14
15
|
const ai = new GoogleGenAI({
|
|
15
16
|
apiKey: process.env.GEMINI_API_KEY || "",
|
|
@@ -166,30 +167,44 @@ async function processVideoInput(
|
|
|
166
167
|
export const geminiTextToImage = {
|
|
167
168
|
name: "generateImage",
|
|
168
169
|
description:
|
|
169
|
-
"Generate images from text prompts using Gemini
|
|
170
|
+
"Generate images from text prompts using Gemini image models with optional reference images",
|
|
170
171
|
parameters: z.object({
|
|
171
172
|
prompt: z.string().describe("Text description of the image to generate"),
|
|
172
173
|
aspect_ratio: z
|
|
173
174
|
.string()
|
|
174
175
|
.optional()
|
|
175
|
-
.describe("Aspect ratio: 1:1, 3:4, 4:3, 9:16, or 16:9"),
|
|
176
|
+
.describe("Aspect ratio: 1:1, 3:4, 4:3, 9:16, or 16:9 (default 9:16)"),
|
|
176
177
|
output_path: z
|
|
177
178
|
.string()
|
|
178
179
|
.optional()
|
|
179
180
|
.describe("File path to save the generated image"),
|
|
181
|
+
reference_images: z
|
|
182
|
+
.array(z.string())
|
|
183
|
+
.optional()
|
|
184
|
+
.describe("Optional reference image file paths to guide generation"),
|
|
180
185
|
}),
|
|
181
186
|
execute: async (args: {
|
|
182
187
|
prompt: string;
|
|
183
188
|
aspect_ratio?: string;
|
|
184
189
|
output_path?: string;
|
|
190
|
+
reference_images?: string[];
|
|
185
191
|
}) => {
|
|
186
192
|
try {
|
|
193
|
+
const contents: any[] = [args.prompt];
|
|
194
|
+
|
|
195
|
+
if (args.reference_images && Array.isArray(args.reference_images)) {
|
|
196
|
+
for (const refPath of args.reference_images) {
|
|
197
|
+
contents.push(await fileToGenerativePart(refPath));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
187
201
|
const response = await ai.models.generateContent({
|
|
188
|
-
model: "gemini-
|
|
189
|
-
contents:
|
|
202
|
+
model: "gemini-3-pro-image-preview",
|
|
203
|
+
contents: contents,
|
|
190
204
|
config: {
|
|
205
|
+
responseModalities: ["TEXT", "IMAGE"],
|
|
191
206
|
imageConfig: {
|
|
192
|
-
aspectRatio: args.aspect_ratio || "
|
|
207
|
+
aspectRatio: args.aspect_ratio || "9:16",
|
|
193
208
|
},
|
|
194
209
|
},
|
|
195
210
|
});
|
|
@@ -205,13 +220,16 @@ export const geminiTextToImage = {
|
|
|
205
220
|
const imageData = part.inlineData.data;
|
|
206
221
|
if (args.output_path) {
|
|
207
222
|
const storage = getStorage();
|
|
223
|
+
const timestampedPath = generateTimestampedFilename(
|
|
224
|
+
args.output_path
|
|
225
|
+
);
|
|
208
226
|
const url = await storage.writeFile(
|
|
209
|
-
|
|
227
|
+
timestampedPath,
|
|
210
228
|
Buffer.from(imageData, "base64")
|
|
211
229
|
);
|
|
212
230
|
images.push({
|
|
213
231
|
url,
|
|
214
|
-
filename:
|
|
232
|
+
filename: timestampedPath,
|
|
215
233
|
mimeType: "image/png",
|
|
216
234
|
});
|
|
217
235
|
}
|
|
@@ -238,7 +256,7 @@ export const geminiTextToImage = {
|
|
|
238
256
|
export const geminiEditImage = {
|
|
239
257
|
name: "editImage",
|
|
240
258
|
description:
|
|
241
|
-
"Edit existing images with text instructions using Gemini
|
|
259
|
+
"Edit existing images with text instructions using Gemini 3 Pro Image Preview",
|
|
242
260
|
parameters: z.object({
|
|
243
261
|
image_path: z.string().describe("Path to the source image file"),
|
|
244
262
|
prompt: z.string().describe("Text instructions for editing the image"),
|
|
@@ -268,7 +286,7 @@ export const geminiEditImage = {
|
|
|
268
286
|
}
|
|
269
287
|
|
|
270
288
|
const response = await ai.models.generateContent({
|
|
271
|
-
model: "gemini-
|
|
289
|
+
model: "gemini-3-pro-image-preview",
|
|
272
290
|
contents: contents,
|
|
273
291
|
});
|
|
274
292
|
|
|
@@ -283,13 +301,16 @@ export const geminiEditImage = {
|
|
|
283
301
|
const imageData = part.inlineData.data;
|
|
284
302
|
if (args.output_path) {
|
|
285
303
|
const storage = getStorage();
|
|
304
|
+
const timestampedPath = generateTimestampedFilename(
|
|
305
|
+
args.output_path
|
|
306
|
+
);
|
|
286
307
|
const url = await storage.writeFile(
|
|
287
|
-
|
|
308
|
+
timestampedPath,
|
|
288
309
|
Buffer.from(imageData, "base64")
|
|
289
310
|
);
|
|
290
311
|
images.push({
|
|
291
312
|
url,
|
|
292
|
-
filename:
|
|
313
|
+
filename: timestampedPath,
|
|
293
314
|
mimeType: "image/png",
|
|
294
315
|
});
|
|
295
316
|
}
|
|
@@ -425,10 +446,11 @@ export const geminiSingleSpeakerTts = {
|
|
|
425
446
|
const audioBuffer = Buffer.from(data, "base64");
|
|
426
447
|
|
|
427
448
|
// Generate output filename if not provided
|
|
428
|
-
const outputPath = args.output_path ||
|
|
449
|
+
const outputPath = args.output_path || "voice_output.wav";
|
|
450
|
+
const timestampedPath = generateTimestampedFilename(outputPath);
|
|
429
451
|
|
|
430
452
|
const storage = getStorage();
|
|
431
|
-
const url = await storage.writeFile(
|
|
453
|
+
const url = await storage.writeFile(timestampedPath, audioBuffer);
|
|
432
454
|
|
|
433
455
|
return JSON.stringify({
|
|
434
456
|
audio: {
|
|
@@ -4,6 +4,7 @@ import { exec } from "child_process";
|
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { getStorage } from "../storage";
|
|
7
|
+
import { generateTimestampedFilename } from "../utils/filename";
|
|
7
8
|
|
|
8
9
|
async function wait(ms: number): Promise<void> {
|
|
9
10
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -287,11 +288,13 @@ export const imageToVideo = {
|
|
|
287
288
|
[];
|
|
288
289
|
const saveVideo = async (base64: string, index: number) => {
|
|
289
290
|
if (!base64) return;
|
|
290
|
-
const
|
|
291
|
+
const baseFilename = args.output_path
|
|
291
292
|
? index === 0
|
|
292
293
|
? args.output_path
|
|
293
294
|
: args.output_path.replace(/\.mp4$/i, `_${index}.mp4`)
|
|
294
|
-
: `
|
|
295
|
+
: `video_output${index > 0 ? `_${index}` : ""}.mp4`;
|
|
296
|
+
|
|
297
|
+
const filePath = generateTimestampedFilename(baseFilename);
|
|
295
298
|
|
|
296
299
|
const buf = Buffer.from(base64, "base64");
|
|
297
300
|
const storage = getStorage();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a timestamped filename to avoid conflicts
|
|
3
|
+
* Format: YYYYMMDD_HHmmss_filename.ext
|
|
4
|
+
*/
|
|
5
|
+
export function generateTimestampedFilename(basename: string): string {
|
|
6
|
+
const now = new Date();
|
|
7
|
+
const timestamp = now
|
|
8
|
+
.toISOString()
|
|
9
|
+
.replace(/[-:]/g, "")
|
|
10
|
+
.replace(/\.\d{3}Z$/, "")
|
|
11
|
+
.replace("T", "_");
|
|
12
|
+
|
|
13
|
+
// Extract extension if present
|
|
14
|
+
const lastDot = basename.lastIndexOf(".");
|
|
15
|
+
if (lastDot > 0) {
|
|
16
|
+
const name = basename.substring(0, lastDot);
|
|
17
|
+
const ext = basename.substring(lastDot);
|
|
18
|
+
return `${timestamp}_${name}${ext}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return `${timestamp}_${basename}`;
|
|
22
|
+
}
|