@mixio-pro/kalaasetu-mcp 1.0.16 → 1.0.18
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 +45 -44
- package/src/tools/image-to-video.ts +22 -21
- package/src/utils/url-file.ts +91 -0
package/package.json
CHANGED
package/src/tools/gemini.ts
CHANGED
|
@@ -16,32 +16,32 @@ const ai = new GoogleGenAI({
|
|
|
16
16
|
apiKey: process.env.GEMINI_API_KEY || "",
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
+
import { ensureLocalFile } from "../utils/url-file";
|
|
20
|
+
|
|
19
21
|
async function fileToGenerativePart(filePath: string) {
|
|
20
|
-
const
|
|
22
|
+
const fileResult = await ensureLocalFile(filePath);
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
`Is absolute: ${isAbsolute}\n` +
|
|
34
|
-
`CWD: ${process.cwd()}`
|
|
35
|
-
);
|
|
36
|
-
}
|
|
24
|
+
try {
|
|
25
|
+
let imageBytes: Buffer;
|
|
26
|
+
if (fileResult.isTemp) {
|
|
27
|
+
// Temp files are always local on disk, read directly with fs
|
|
28
|
+
// This avoids 404s if storage provider is GCS/S3
|
|
29
|
+
imageBytes = fs.readFileSync(fileResult.path);
|
|
30
|
+
} else {
|
|
31
|
+
// If not temp, it might be a storage path, let storage provider handle it
|
|
32
|
+
const storage = getStorage();
|
|
33
|
+
imageBytes = await storage.readFile(fileResult.path);
|
|
34
|
+
}
|
|
37
35
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
}
|
|
36
|
+
return {
|
|
37
|
+
inlineData: {
|
|
38
|
+
data: Buffer.from(imageBytes).toString("base64"),
|
|
39
|
+
mimeType: "image/jpeg",
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
} finally {
|
|
43
|
+
fileResult.cleanup();
|
|
44
|
+
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// Helper function to save WAV file
|
|
@@ -165,29 +165,30 @@ async function processVideoInput(
|
|
|
165
165
|
},
|
|
166
166
|
};
|
|
167
167
|
} else {
|
|
168
|
-
// Local file processing - use File Upload API
|
|
169
|
-
|
|
168
|
+
// Local file processing or non-YouTube URL - use File Upload API
|
|
169
|
+
// ensureLocalFile handles downloading URLs if needed
|
|
170
|
+
const fileResult = await ensureLocalFile(input);
|
|
170
171
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
172
|
+
try {
|
|
173
|
+
// Upload file to Gemini API
|
|
174
|
+
// If it's a temp file, we should bypass storage.readFile logic in uploadFileToGemini
|
|
175
|
+
// OR we can rely on uploadFileToGemini's existence check.
|
|
176
|
+
// But uploadFileToGemini uses storage.readFile if !fs.exists.
|
|
177
|
+
// Since fileResult.path IS a local temp file, fs.exists WILL be true.
|
|
178
|
+
// So uploadFileToGemini will see it exists locally and NOT try storage.readFile.
|
|
179
|
+
// Wait, uploadFileToGemini takes `filePath`.
|
|
180
|
+
// If `filePath` is absolute local path, `fs.existsSync` is true.
|
|
181
|
+
// So no changes needed here?
|
|
182
|
+
// Let's verify uploadFileToGemini logic again.
|
|
183
|
+
// It does: if (!fs.existsSync(filePath)) { read from storage }
|
|
184
|
+
// fileResult.path is guaranteed to exist locally (if temp).
|
|
185
|
+
// So correct.
|
|
186
|
+
|
|
187
|
+
const uploadedFile = await uploadFileToGemini(fileResult.path);
|
|
188
|
+
return uploadedFile;
|
|
189
|
+
} finally {
|
|
190
|
+
fileResult.cleanup();
|
|
185
191
|
}
|
|
186
|
-
|
|
187
|
-
// Upload file to Gemini API
|
|
188
|
-
const uploadedFile = await uploadFileToGemini(input);
|
|
189
|
-
|
|
190
|
-
return uploadedFile;
|
|
191
192
|
}
|
|
192
193
|
}
|
|
193
194
|
|
|
@@ -11,32 +11,33 @@ async function wait(ms: number): Promise<void> {
|
|
|
11
11
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
import { ensureLocalFile } from "../utils/url-file";
|
|
15
|
+
|
|
14
16
|
async function fileToBase64(
|
|
15
17
|
filePath: string
|
|
16
18
|
): Promise<{ data: string; mimeType: string }> {
|
|
17
|
-
const
|
|
19
|
+
const fileResult = await ensureLocalFile(filePath);
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
);
|
|
33
|
-
}
|
|
21
|
+
try {
|
|
22
|
+
let data: string;
|
|
23
|
+
if (fileResult.isTemp) {
|
|
24
|
+
// Temp files are always local on disk, read directly with fs
|
|
25
|
+
// This avoids 404s if storage provider is GCS
|
|
26
|
+
const fs = await import("fs");
|
|
27
|
+
const buf = fs.readFileSync(fileResult.path);
|
|
28
|
+
data = Buffer.from(buf).toString("base64");
|
|
29
|
+
} else {
|
|
30
|
+
const storage = getStorage();
|
|
31
|
+
const buf = await storage.readFile(fileResult.path);
|
|
32
|
+
data = Buffer.from(buf).toString("base64");
|
|
33
|
+
}
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
// Default to PNG if not sure, similar to existing code
|
|
36
|
+
const mimeType = "image/png";
|
|
37
|
+
return { data, mimeType };
|
|
38
|
+
} finally {
|
|
39
|
+
fileResult.cleanup();
|
|
40
|
+
}
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
export const imageToVideo = {
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
import { getStorage } from "../storage";
|
|
5
|
+
import { generateTimestampedFilename } from "./filename";
|
|
6
|
+
|
|
7
|
+
export interface LocalFileResult {
|
|
8
|
+
path: string;
|
|
9
|
+
isTemp: boolean;
|
|
10
|
+
cleanup: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Ensures the input is a local file path.
|
|
15
|
+
* If input is a URL, downloads it to a temporary file.
|
|
16
|
+
* If input is a file path, returns it as is (resolving if necessary).
|
|
17
|
+
*/
|
|
18
|
+
export async function ensureLocalFile(input: string): Promise<LocalFileResult> {
|
|
19
|
+
const isUrl = input.startsWith("http://") || input.startsWith("https://");
|
|
20
|
+
|
|
21
|
+
if (isUrl) {
|
|
22
|
+
const tempDir = os.tmpdir();
|
|
23
|
+
// Use a timestamped filename to avoid collisions
|
|
24
|
+
// Try to guess extension from URL or default to bin/tmp
|
|
25
|
+
const urlPath = new URL(input).pathname;
|
|
26
|
+
const ext = path.extname(urlPath) || ".tmp";
|
|
27
|
+
// We'll use a simple name since generateTimestampedFilename expects a file path
|
|
28
|
+
const safeName = `mcp_download_${Date.now()}${ext}`;
|
|
29
|
+
const tempFilePath = path.join(tempDir, safeName);
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch(input);
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Failed to download URL: ${input} (${response.status} ${response.statusText})`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
40
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
41
|
+
fs.writeFileSync(tempFilePath, buffer);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
path: tempFilePath,
|
|
45
|
+
isTemp: true,
|
|
46
|
+
cleanup: () => {
|
|
47
|
+
try {
|
|
48
|
+
if (fs.existsSync(tempFilePath)) {
|
|
49
|
+
fs.unlinkSync(tempFilePath);
|
|
50
|
+
}
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.error(`Failed to cleanup temp file ${tempFilePath}:`, e);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
} catch (error: any) {
|
|
57
|
+
// If download failed, clean up if we created the file
|
|
58
|
+
try {
|
|
59
|
+
if (fs.existsSync(tempFilePath)) {
|
|
60
|
+
fs.unlinkSync(tempFilePath);
|
|
61
|
+
}
|
|
62
|
+
} catch {}
|
|
63
|
+
throw new Error(`Failed to process URL ${input}: ${error.message}`);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
// It's a local file path (or at least we treat it as one)
|
|
67
|
+
// We verify existence using our storage abstraction or fs
|
|
68
|
+
const storage = getStorage();
|
|
69
|
+
const exists = await storage.exists(input);
|
|
70
|
+
|
|
71
|
+
if (!exists) {
|
|
72
|
+
// Try to provide more helpful error information
|
|
73
|
+
const isAbsolute = path.isAbsolute(input);
|
|
74
|
+
const resolvedPath = isAbsolute
|
|
75
|
+
? input
|
|
76
|
+
: path.resolve(process.cwd(), input);
|
|
77
|
+
throw new Error(
|
|
78
|
+
`File not found: ${input}\n` +
|
|
79
|
+
`Resolved path: ${resolvedPath}\n` +
|
|
80
|
+
`Is absolute: ${isAbsolute}\n` +
|
|
81
|
+
`CWD: ${process.cwd()}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
path: input,
|
|
87
|
+
isTemp: false,
|
|
88
|
+
cleanup: () => {}, // No-op for existing local files
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|