@mixio-pro/kalaasetu-mcp 1.0.13 → 1.0.15
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 +11 -7
- package/src/tools/gemini.ts +30 -21
- package/src/tools/image-to-video.ts +3 -41
- package/src/utils/google-auth.ts +83 -0
package/package.json
CHANGED
package/src/storage/gcs.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { GoogleAuth } from "google-auth-library";
|
|
2
2
|
import type { StorageProvider } from "./interface";
|
|
3
3
|
import * as path from "path";
|
|
4
|
+
import {
|
|
5
|
+
getGoogleAuthClient,
|
|
6
|
+
getGoogleAccessToken,
|
|
7
|
+
} from "../utils/google-auth";
|
|
4
8
|
|
|
5
9
|
export class GCSStorageProvider implements StorageProvider {
|
|
6
10
|
private bucket: string;
|
|
@@ -8,7 +12,7 @@ export class GCSStorageProvider implements StorageProvider {
|
|
|
8
12
|
|
|
9
13
|
constructor(bucket: string) {
|
|
10
14
|
this.bucket = bucket;
|
|
11
|
-
this.auth =
|
|
15
|
+
this.auth = getGoogleAuthClient({
|
|
12
16
|
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
|
|
13
17
|
});
|
|
14
18
|
}
|
|
@@ -26,12 +30,12 @@ export class GCSStorageProvider implements StorageProvider {
|
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
private async getAccessToken(): Promise<string> {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
// Use the centralized helper which includes gcloud fallback if needed,
|
|
34
|
+
// although GCSStorageProvider primarily relies on the initialized GoogleAuth.
|
|
35
|
+
// However, if we want to support the same fallback logic as tools:
|
|
36
|
+
return getGoogleAccessToken({
|
|
37
|
+
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
|
|
38
|
+
});
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
async readFile(filePath: string): Promise<Buffer> {
|
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);
|
|
@@ -5,50 +5,12 @@ import { z } from "zod";
|
|
|
5
5
|
import { getStorage } from "../storage";
|
|
6
6
|
import { generateTimestampedFilename } from "../utils/filename";
|
|
7
7
|
|
|
8
|
+
import { getGoogleAccessToken } from "../utils/google-auth";
|
|
9
|
+
|
|
8
10
|
async function wait(ms: number): Promise<void> {
|
|
9
11
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
async function fetchAccessToken(): Promise<string> {
|
|
13
|
-
try {
|
|
14
|
-
const auth = new GoogleAuth({
|
|
15
|
-
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
|
|
16
|
-
});
|
|
17
|
-
const client = await auth.getClient();
|
|
18
|
-
const token = await client.getAccessToken();
|
|
19
|
-
if (!token || typeof token !== "string") {
|
|
20
|
-
throw new Error("No token from GoogleAuth");
|
|
21
|
-
}
|
|
22
|
-
return token;
|
|
23
|
-
} catch (e) {
|
|
24
|
-
// Fallback to gcloud
|
|
25
|
-
return await new Promise((resolve, reject) => {
|
|
26
|
-
exec("gcloud auth print-access-token", (err, stdout, stderr) => {
|
|
27
|
-
if (err) {
|
|
28
|
-
reject(
|
|
29
|
-
new Error(
|
|
30
|
-
`Failed to fetch an access token (ADC and gcloud): ${
|
|
31
|
-
stderr || err.message
|
|
32
|
-
}`
|
|
33
|
-
)
|
|
34
|
-
);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const t = (stdout || "").trim();
|
|
38
|
-
if (!t) {
|
|
39
|
-
reject(
|
|
40
|
-
new Error(
|
|
41
|
-
"Failed to fetch an access token: empty token from gcloud"
|
|
42
|
-
)
|
|
43
|
-
);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
resolve(t);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
14
|
async function fileToBase64(
|
|
53
15
|
filePath: string
|
|
54
16
|
): Promise<{ data: string; mimeType: string }> {
|
|
@@ -191,7 +153,7 @@ export const imageToVideo = {
|
|
|
191
153
|
durationSeconds = 8;
|
|
192
154
|
}
|
|
193
155
|
|
|
194
|
-
const token = await
|
|
156
|
+
const token = await getGoogleAccessToken();
|
|
195
157
|
|
|
196
158
|
const url = `https://${location}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${location}/publishers/google/models/${modelId}:predictLongRunning`;
|
|
197
159
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { GoogleAuth } from "google-auth-library";
|
|
2
|
+
import { exec } from "child_process";
|
|
3
|
+
|
|
4
|
+
export interface GoogleAuthOptions {
|
|
5
|
+
scopes?: string | string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Returns a GoogleAuth client, prioritizing GOOGLE_SERVICE_ACCOUNT_JSON env var.
|
|
10
|
+
*/
|
|
11
|
+
export function getGoogleAuthClient(
|
|
12
|
+
options: GoogleAuthOptions = {}
|
|
13
|
+
): GoogleAuth {
|
|
14
|
+
const opts: any = {
|
|
15
|
+
scopes: options.scopes || [
|
|
16
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
if (process.env.GOOGLE_SERVICE_ACCOUNT_JSON) {
|
|
21
|
+
try {
|
|
22
|
+
const credentials = JSON.parse(process.env.GOOGLE_SERVICE_ACCOUNT_JSON);
|
|
23
|
+
opts.credentials = credentials;
|
|
24
|
+
if (credentials.project_id) {
|
|
25
|
+
opts.projectId = credentials.project_id;
|
|
26
|
+
}
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.warn(
|
|
29
|
+
"Failed to parse GOOGLE_SERVICE_ACCOUNT_JSON, falling back to other methods:",
|
|
30
|
+
error
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
} else if (process.env.GOOGLE_SERVICE_ACCOUNT_JSON_PATH) {
|
|
34
|
+
// GoogleAuth natively supports keyFile/keyFilename
|
|
35
|
+
opts.keyFile = process.env.GOOGLE_SERVICE_ACCOUNT_JSON_PATH;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return new GoogleAuth(opts);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Returns an access token, trying GoogleAuth first (JSON > ADC), then gcloud CLI.
|
|
43
|
+
*/
|
|
44
|
+
export async function getGoogleAccessToken(
|
|
45
|
+
options: GoogleAuthOptions = {}
|
|
46
|
+
): Promise<string> {
|
|
47
|
+
try {
|
|
48
|
+
const auth = getGoogleAuthClient(options);
|
|
49
|
+
const client = await auth.getClient();
|
|
50
|
+
const token = await client.getAccessToken();
|
|
51
|
+
if (token.token) {
|
|
52
|
+
return token.token;
|
|
53
|
+
}
|
|
54
|
+
throw new Error("No token returned from GoogleAuth");
|
|
55
|
+
} catch (e) {
|
|
56
|
+
// Fallback to gcloud
|
|
57
|
+
console.warn("GoogleAuth failed, falling back to gcloud CLI:", e);
|
|
58
|
+
return await new Promise((resolve, reject) => {
|
|
59
|
+
exec("gcloud auth print-access-token", (err, stdout, stderr) => {
|
|
60
|
+
if (err) {
|
|
61
|
+
reject(
|
|
62
|
+
new Error(
|
|
63
|
+
`Failed to fetch an access token (Auth Library and gcloud): ${
|
|
64
|
+
stderr || err.message
|
|
65
|
+
}`
|
|
66
|
+
)
|
|
67
|
+
);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const t = (stdout || "").trim();
|
|
71
|
+
if (!t) {
|
|
72
|
+
reject(
|
|
73
|
+
new Error(
|
|
74
|
+
"Failed to fetch an access token: empty token from gcloud"
|
|
75
|
+
)
|
|
76
|
+
);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
resolve(t);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|