@mixio-pro/kalaasetu-mcp 1.0.12 → 1.0.14
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/image-to-video.ts +38 -48
- 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> {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
1
|
import { GoogleAuth } from "google-auth-library";
|
|
3
2
|
import { exec } from "child_process";
|
|
4
3
|
import * as path from "path";
|
|
@@ -6,50 +5,12 @@ import { z } from "zod";
|
|
|
6
5
|
import { getStorage } from "../storage";
|
|
7
6
|
import { generateTimestampedFilename } from "../utils/filename";
|
|
8
7
|
|
|
8
|
+
import { getGoogleAccessToken } from "../utils/google-auth";
|
|
9
|
+
|
|
9
10
|
async function wait(ms: number): Promise<void> {
|
|
10
11
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
async function fetchAccessToken(): Promise<string> {
|
|
14
|
-
try {
|
|
15
|
-
const auth = new GoogleAuth({
|
|
16
|
-
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
|
|
17
|
-
});
|
|
18
|
-
const client = await auth.getClient();
|
|
19
|
-
const token = await client.getAccessToken();
|
|
20
|
-
if (!token || typeof token !== "string") {
|
|
21
|
-
throw new Error("No token from GoogleAuth");
|
|
22
|
-
}
|
|
23
|
-
return token;
|
|
24
|
-
} catch (e) {
|
|
25
|
-
// Fallback to gcloud
|
|
26
|
-
return await new Promise((resolve, reject) => {
|
|
27
|
-
exec("gcloud auth print-access-token", (err, stdout, stderr) => {
|
|
28
|
-
if (err) {
|
|
29
|
-
reject(
|
|
30
|
-
new Error(
|
|
31
|
-
`Failed to fetch an access token (ADC and gcloud): ${
|
|
32
|
-
stderr || err.message
|
|
33
|
-
}`
|
|
34
|
-
)
|
|
35
|
-
);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
const t = (stdout || "").trim();
|
|
39
|
-
if (!t) {
|
|
40
|
-
reject(
|
|
41
|
-
new Error(
|
|
42
|
-
"Failed to fetch an access token: empty token from gcloud"
|
|
43
|
-
)
|
|
44
|
-
);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
resolve(t);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
14
|
async function fileToBase64(
|
|
54
15
|
filePath: string
|
|
55
16
|
): Promise<{ data: string; mimeType: string }> {
|
|
@@ -99,7 +60,9 @@ export const imageToVideo = {
|
|
|
99
60
|
duration_seconds: z
|
|
100
61
|
.string()
|
|
101
62
|
.optional()
|
|
102
|
-
.describe(
|
|
63
|
+
.describe(
|
|
64
|
+
"Video duration in seconds. MUST be one of: '4', '6', or '8' (default: '6'). Other values will be rejected by Vertex AI."
|
|
65
|
+
),
|
|
103
66
|
resolution: z
|
|
104
67
|
.string()
|
|
105
68
|
.optional()
|
|
@@ -164,7 +127,33 @@ export const imageToVideo = {
|
|
|
164
127
|
const location = args.location_id || "us-central1";
|
|
165
128
|
const modelId = args.model_id || "veo-3.1-fast-generate-preview";
|
|
166
129
|
|
|
167
|
-
|
|
130
|
+
// Validate and parse duration_seconds - snap to nearest 4, 6, or 8
|
|
131
|
+
let durationSeconds = parseInt(args.duration_seconds || "6");
|
|
132
|
+
if (isNaN(durationSeconds)) durationSeconds = 6;
|
|
133
|
+
|
|
134
|
+
const validDurations = [4, 6, 8];
|
|
135
|
+
// Find nearest valid duration
|
|
136
|
+
durationSeconds = validDurations.reduce((prev, curr) => {
|
|
137
|
+
return Math.abs(curr - durationSeconds) < Math.abs(prev - durationSeconds)
|
|
138
|
+
? curr
|
|
139
|
+
: prev;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Tie-breaking: if equidistant (e.g. 5), the reduce above keeps the first one (4) because < is strict.
|
|
143
|
+
// However, user requested "nearest duration with the ceil", effectively meaning round up if equidistant.
|
|
144
|
+
// Let's explicitly handle the equidistant cases or just use a custom finder.
|
|
145
|
+
// 5 -> equidistant to 4 and 6. "With ceil" implies 6.
|
|
146
|
+
// 7 -> equidistant to 6 and 8. "With ceil" implies 8.
|
|
147
|
+
|
|
148
|
+
// Simpler logic for these specific values:
|
|
149
|
+
if (durationSeconds === 4 && parseInt(args.duration_seconds || "6") === 5) {
|
|
150
|
+
durationSeconds = 6;
|
|
151
|
+
}
|
|
152
|
+
if (durationSeconds === 6 && parseInt(args.duration_seconds || "6") === 7) {
|
|
153
|
+
durationSeconds = 8;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const token = await getGoogleAccessToken();
|
|
168
157
|
|
|
169
158
|
const url = `https://${location}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${location}/publishers/google/models/${modelId}:predictLongRunning`;
|
|
170
159
|
|
|
@@ -242,7 +231,7 @@ export const imageToVideo = {
|
|
|
242
231
|
|
|
243
232
|
const parameters: any = {
|
|
244
233
|
aspectRatio: args.aspect_ratio || "9:16",
|
|
245
|
-
durationSeconds:
|
|
234
|
+
durationSeconds: durationSeconds,
|
|
246
235
|
resolution: args.resolution || "720p",
|
|
247
236
|
negativePrompt: args.negative_prompt,
|
|
248
237
|
generateAudio: args.generate_audio || false,
|
|
@@ -302,14 +291,15 @@ export const imageToVideo = {
|
|
|
302
291
|
[];
|
|
303
292
|
const saveVideo = async (base64: string, index: number) => {
|
|
304
293
|
if (!base64) return;
|
|
305
|
-
|
|
294
|
+
|
|
306
295
|
// Use provided output path or generate default with timestamp
|
|
307
296
|
let filePath: string;
|
|
308
297
|
if (args.output_path) {
|
|
309
298
|
// User provided path - use as-is for first video, add index for subsequent
|
|
310
|
-
filePath =
|
|
311
|
-
|
|
312
|
-
|
|
299
|
+
filePath =
|
|
300
|
+
index === 0
|
|
301
|
+
? args.output_path
|
|
302
|
+
: args.output_path.replace(/\.mp4$/i, `_${index}.mp4`);
|
|
313
303
|
} else {
|
|
314
304
|
// No path provided - generate timestamped default
|
|
315
305
|
const defaultName = `video_output${index > 0 ? `_${index}` : ""}.mp4`;
|
|
@@ -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
|
+
}
|