@hasna/connectors 1.3.18 → 1.3.20
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/bin/index.js +28 -8
- package/bin/mcp.js +8 -1
- package/bin/serve.js +24 -4
- package/connectors/connect-googlegemini/src/api/images.ts +0 -3
- package/connectors/connect-googlegemini/src/cli/index.ts +0 -2
- package/connectors/connect-googlegemini/src/types/index.ts +0 -1
- package/connectors/connect-minimax/CLAUDE.md +47 -0
- package/connectors/connect-minimax/package.json +41 -0
- package/connectors/connect-minimax/src/api/client.ts +123 -0
- package/connectors/connect-minimax/src/api/image.ts +65 -0
- package/connectors/connect-minimax/src/api/index.ts +53 -0
- package/connectors/connect-minimax/src/api/music.ts +78 -0
- package/connectors/connect-minimax/src/api/sound-effects.ts +59 -0
- package/connectors/connect-minimax/src/api/tts.ts +56 -0
- package/connectors/connect-minimax/src/api/video.ts +78 -0
- package/connectors/connect-minimax/src/cli/index.ts +246 -0
- package/connectors/connect-minimax/src/index.ts +19 -0
- package/connectors/connect-minimax/src/types/index.ts +182 -0
- package/connectors/connect-minimax/src/utils/config.ts +125 -0
- package/connectors/connect-minimax/src/utils/output.ts +40 -0
- package/connectors/connect-minimax/tsconfig.json +8 -0
- package/dist/index.js +8 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/serve.d.ts +1 -0
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -1909,7 +1909,7 @@ var package_default;
|
|
|
1909
1909
|
var init_package = __esm(() => {
|
|
1910
1910
|
package_default = {
|
|
1911
1911
|
name: "@hasna/connectors",
|
|
1912
|
-
version: "1.3.
|
|
1912
|
+
version: "1.3.20",
|
|
1913
1913
|
description: "Open source connector library - Install API connectors with a single command",
|
|
1914
1914
|
type: "module",
|
|
1915
1915
|
bin: {
|
|
@@ -20262,6 +20262,13 @@ var init_ai_ml = __esm(() => {
|
|
|
20262
20262
|
category: "AI & ML",
|
|
20263
20263
|
tags: ["ai", "llm", "grok"]
|
|
20264
20264
|
},
|
|
20265
|
+
{
|
|
20266
|
+
name: "minimax",
|
|
20267
|
+
displayName: "Minimax",
|
|
20268
|
+
description: "Video, music, image, TTS, and sound effects generation",
|
|
20269
|
+
category: "AI & ML",
|
|
20270
|
+
tags: ["ai", "video", "music", "tts", "image", "sound-effects"]
|
|
20271
|
+
},
|
|
20265
20272
|
{
|
|
20266
20273
|
name: "mistral",
|
|
20267
20274
|
displayName: "Mistral",
|
|
@@ -29837,7 +29844,19 @@ function serveStaticFile(filePath) {
|
|
|
29837
29844
|
headers: { "Content-Type": contentType }
|
|
29838
29845
|
});
|
|
29839
29846
|
}
|
|
29840
|
-
async function findAvailablePort(preferred) {
|
|
29847
|
+
async function findAvailablePort(preferred, strict = false) {
|
|
29848
|
+
if (strict) {
|
|
29849
|
+
try {
|
|
29850
|
+
const server = Bun.serve({ port: preferred, fetch() {
|
|
29851
|
+
return new Response;
|
|
29852
|
+
} });
|
|
29853
|
+
server.stop(true);
|
|
29854
|
+
return preferred;
|
|
29855
|
+
} catch {
|
|
29856
|
+
throw new Error(`Port ${preferred} is already in use. OAuth requires a fixed port.
|
|
29857
|
+
` + `Free the port and try again: lsof -ti :${preferred} | xargs kill`);
|
|
29858
|
+
}
|
|
29859
|
+
}
|
|
29841
29860
|
for (let port = preferred;port < preferred + 100; port++) {
|
|
29842
29861
|
try {
|
|
29843
29862
|
const server = Bun.serve({ port, fetch() {
|
|
@@ -29851,6 +29870,7 @@ async function findAvailablePort(preferred) {
|
|
|
29851
29870
|
}
|
|
29852
29871
|
async function startServer(requestedPort, options) {
|
|
29853
29872
|
const shouldOpen = options?.open ?? true;
|
|
29873
|
+
const strict = options?.strict ?? false;
|
|
29854
29874
|
loadConnectorVersions();
|
|
29855
29875
|
const dashboardDir = resolveDashboardDir();
|
|
29856
29876
|
const dashboardExists = existsSync17(dashboardDir);
|
|
@@ -29866,7 +29886,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
29866
29886
|
console.error(` bun run build:dashboard
|
|
29867
29887
|
`);
|
|
29868
29888
|
}
|
|
29869
|
-
const port = await findAvailablePort(requestedPort);
|
|
29889
|
+
const port = await findAvailablePort(requestedPort, strict);
|
|
29870
29890
|
if (port !== requestedPort) {
|
|
29871
29891
|
console.log(`Port ${requestedPort} is in use, using port ${port} instead`);
|
|
29872
29892
|
}
|
|
@@ -32665,7 +32685,7 @@ Categories:
|
|
|
32665
32685
|
console.log(` ${category} (${count})`);
|
|
32666
32686
|
}
|
|
32667
32687
|
});
|
|
32668
|
-
program2.command("serve").alias("dashboard").alias("open").option("-p, --port <port>", "Port to run the dashboard on", "
|
|
32688
|
+
program2.command("serve").alias("dashboard").alias("open").option("-p, --port <port>", "Port to run the dashboard on", "9876").option("--open", "Open dashboard in browser (default)", true).option("--no-open", "Don't open browser automatically").description("Start local dashboard for connector auth management").action(async (options) => {
|
|
32669
32689
|
const port = parseInt(options.port, 10);
|
|
32670
32690
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
32671
32691
|
console.log(chalk3.red("Invalid port number"));
|
|
@@ -33050,10 +33070,10 @@ ${meta.displayName} \u2014 Auth Configuration
|
|
|
33050
33070
|
console.log(chalk5.yellow("OAuth connectors require browser-based authentication."));
|
|
33051
33071
|
console.log();
|
|
33052
33072
|
try {
|
|
33053
|
-
const port =
|
|
33073
|
+
const port = 9876;
|
|
33054
33074
|
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
|
|
33055
33075
|
console.log(chalk5.dim(`Starting temporary server on port ${port}...`));
|
|
33056
|
-
await startServer2(port, { open: false });
|
|
33076
|
+
await startServer2(port, { open: false, strict: true });
|
|
33057
33077
|
const oauthUrl = `http://localhost:${port}/oauth/${connector}/start`;
|
|
33058
33078
|
console.log(chalk5.bold(`
|
|
33059
33079
|
Open this URL to authenticate:
|
|
@@ -34327,9 +34347,9 @@ Setting up ${meta.displayName}...
|
|
|
34327
34347
|
}
|
|
34328
34348
|
console.log(` ${chalk7.yellow("\u27F3")} OAuth authentication required \u2014 starting server...`);
|
|
34329
34349
|
try {
|
|
34330
|
-
const port =
|
|
34350
|
+
const port = 9876;
|
|
34331
34351
|
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
|
|
34332
|
-
await startServer2(port, { open: false });
|
|
34352
|
+
await startServer2(port, { open: false, strict: true });
|
|
34333
34353
|
const oauthUrl = `http://localhost:${port}/oauth/${name}/start`;
|
|
34334
34354
|
console.log(`
|
|
34335
34355
|
${chalk7.bold("Open this URL to authenticate:")}`);
|
package/bin/mcp.js
CHANGED
|
@@ -11443,7 +11443,7 @@ var package_default;
|
|
|
11443
11443
|
var init_package = __esm(() => {
|
|
11444
11444
|
package_default = {
|
|
11445
11445
|
name: "@hasna/connectors",
|
|
11446
|
-
version: "1.3.
|
|
11446
|
+
version: "1.3.20",
|
|
11447
11447
|
description: "Open source connector library - Install API connectors with a single command",
|
|
11448
11448
|
type: "module",
|
|
11449
11449
|
bin: {
|
|
@@ -33493,6 +33493,13 @@ var connectors = [
|
|
|
33493
33493
|
category: "AI & ML",
|
|
33494
33494
|
tags: ["ai", "llm", "grok"]
|
|
33495
33495
|
},
|
|
33496
|
+
{
|
|
33497
|
+
name: "minimax",
|
|
33498
|
+
displayName: "Minimax",
|
|
33499
|
+
description: "Video, music, image, TTS, and sound effects generation",
|
|
33500
|
+
category: "AI & ML",
|
|
33501
|
+
tags: ["ai", "video", "music", "tts", "image", "sound-effects"]
|
|
33502
|
+
},
|
|
33496
33503
|
{
|
|
33497
33504
|
name: "mistral",
|
|
33498
33505
|
displayName: "Mistral",
|
package/bin/serve.js
CHANGED
|
@@ -15160,7 +15160,7 @@ var githubConnector = defineConnector({
|
|
|
15160
15160
|
// package.json
|
|
15161
15161
|
var package_default = {
|
|
15162
15162
|
name: "@hasna/connectors",
|
|
15163
|
-
version: "1.3.
|
|
15163
|
+
version: "1.3.20",
|
|
15164
15164
|
description: "Open source connector library - Install API connectors with a single command",
|
|
15165
15165
|
type: "module",
|
|
15166
15166
|
bin: {
|
|
@@ -17221,6 +17221,13 @@ var connectors = [
|
|
|
17221
17221
|
category: "AI & ML",
|
|
17222
17222
|
tags: ["ai", "llm", "grok"]
|
|
17223
17223
|
},
|
|
17224
|
+
{
|
|
17225
|
+
name: "minimax",
|
|
17226
|
+
displayName: "Minimax",
|
|
17227
|
+
description: "Video, music, image, TTS, and sound effects generation",
|
|
17228
|
+
category: "AI & ML",
|
|
17229
|
+
tags: ["ai", "video", "music", "tts", "image", "sound-effects"]
|
|
17230
|
+
},
|
|
17224
17231
|
{
|
|
17225
17232
|
name: "mistral",
|
|
17226
17233
|
displayName: "Mistral",
|
|
@@ -24235,7 +24242,19 @@ function serveStaticFile(filePath) {
|
|
|
24235
24242
|
headers: { "Content-Type": contentType }
|
|
24236
24243
|
});
|
|
24237
24244
|
}
|
|
24238
|
-
async function findAvailablePort(preferred) {
|
|
24245
|
+
async function findAvailablePort(preferred, strict = false) {
|
|
24246
|
+
if (strict) {
|
|
24247
|
+
try {
|
|
24248
|
+
const server = Bun.serve({ port: preferred, fetch() {
|
|
24249
|
+
return new Response;
|
|
24250
|
+
} });
|
|
24251
|
+
server.stop(true);
|
|
24252
|
+
return preferred;
|
|
24253
|
+
} catch {
|
|
24254
|
+
throw new Error(`Port ${preferred} is already in use. OAuth requires a fixed port.
|
|
24255
|
+
` + `Free the port and try again: lsof -ti :${preferred} | xargs kill`);
|
|
24256
|
+
}
|
|
24257
|
+
}
|
|
24239
24258
|
for (let port = preferred;port < preferred + 100; port++) {
|
|
24240
24259
|
try {
|
|
24241
24260
|
const server = Bun.serve({ port, fetch() {
|
|
@@ -24249,6 +24268,7 @@ async function findAvailablePort(preferred) {
|
|
|
24249
24268
|
}
|
|
24250
24269
|
async function startServer(requestedPort, options) {
|
|
24251
24270
|
const shouldOpen = options?.open ?? true;
|
|
24271
|
+
const strict = options?.strict ?? false;
|
|
24252
24272
|
loadConnectorVersions();
|
|
24253
24273
|
const dashboardDir = resolveDashboardDir();
|
|
24254
24274
|
const dashboardExists = existsSync15(dashboardDir);
|
|
@@ -24264,7 +24284,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
24264
24284
|
console.error(` bun run build:dashboard
|
|
24265
24285
|
`);
|
|
24266
24286
|
}
|
|
24267
|
-
const port = await findAvailablePort(requestedPort);
|
|
24287
|
+
const port = await findAvailablePort(requestedPort, strict);
|
|
24268
24288
|
if (port !== requestedPort) {
|
|
24269
24289
|
console.log(`Port ${requestedPort} is in use, using port ${port} instead`);
|
|
24270
24290
|
}
|
|
@@ -24866,7 +24886,7 @@ ${result.stderr}`;
|
|
|
24866
24886
|
}
|
|
24867
24887
|
|
|
24868
24888
|
// src/server/index.ts
|
|
24869
|
-
var DEFAULT_PORT =
|
|
24889
|
+
var DEFAULT_PORT = 9876;
|
|
24870
24890
|
function hasFlag(flag) {
|
|
24871
24891
|
return process.argv.includes(flag);
|
|
24872
24892
|
}
|
|
@@ -15,7 +15,6 @@ export type ImageModel =
|
|
|
15
15
|
export interface ImageGenerationOptions {
|
|
16
16
|
aspectRatio?: ImageConfig['aspectRatio'];
|
|
17
17
|
imageSize?: ImageConfig['imageSize'];
|
|
18
|
-
numberOfImages?: number;
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
export interface GeneratedImage {
|
|
@@ -43,7 +42,6 @@ export class ImagesApi {
|
|
|
43
42
|
imageConfig: {
|
|
44
43
|
aspectRatio: options?.aspectRatio || '16:9',
|
|
45
44
|
imageSize: options?.imageSize || '2K',
|
|
46
|
-
numberOfImages: options?.numberOfImages || 1,
|
|
47
45
|
},
|
|
48
46
|
};
|
|
49
47
|
|
|
@@ -87,7 +85,6 @@ export class ImagesApi {
|
|
|
87
85
|
imageConfig: {
|
|
88
86
|
aspectRatio: options?.aspectRatio,
|
|
89
87
|
imageSize: options?.imageSize,
|
|
90
|
-
numberOfImages: options?.numberOfImages || 1,
|
|
91
88
|
},
|
|
92
89
|
};
|
|
93
90
|
|
|
@@ -289,7 +289,6 @@ imageCmd
|
|
|
289
289
|
model: options.model,
|
|
290
290
|
aspectRatio: options.aspectRatio as ImageConfig['aspectRatio'],
|
|
291
291
|
imageSize: options.imageSize as ImageConfig['imageSize'],
|
|
292
|
-
numberOfImages: parseInt(options.number),
|
|
293
292
|
filenamePrefix: outputName,
|
|
294
293
|
});
|
|
295
294
|
|
|
@@ -300,7 +299,6 @@ imageCmd
|
|
|
300
299
|
model: options.model,
|
|
301
300
|
aspectRatio: options.aspectRatio as ImageConfig['aspectRatio'],
|
|
302
301
|
imageSize: options.size as ImageConfig['imageSize'],
|
|
303
|
-
numberOfImages: parseInt(options.number),
|
|
304
302
|
});
|
|
305
303
|
|
|
306
304
|
success(`Generated ${images.length} image(s)`);
|
|
@@ -81,7 +81,6 @@ export interface GenerationConfig {
|
|
|
81
81
|
export interface ImageConfig {
|
|
82
82
|
aspectRatio?: '1:1' | '2:3' | '3:2' | '3:4' | '4:3' | '4:5' | '5:4' | '9:16' | '16:9' | '21:9';
|
|
83
83
|
imageSize?: '1K' | '2K' | '4K';
|
|
84
|
-
numberOfImages?: number;
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
export interface SpeechConfig {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# connect-minimax
|
|
2
|
+
|
|
3
|
+
Minimax API connector for video, music, image, TTS, and sound effects generation.
|
|
4
|
+
|
|
5
|
+
## API Modules
|
|
6
|
+
|
|
7
|
+
- `VideoApi` — Text-to-video (T2V-01) and image-to-video (I2V-01) generation with async polling
|
|
8
|
+
- `MusicApi` — AI music generation (music-01) with lyrics, genre, mood, tempo control
|
|
9
|
+
- `TTSApi` — Text-to-speech (speech-02-hd) with voice selection, speed, emotion
|
|
10
|
+
- `ImageApi` — Image generation (image-01) with aspect ratio control
|
|
11
|
+
- `SoundEffectsApi` — Sound effect generation from text prompts
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { Minimax } from '@hasna/connect-minimax';
|
|
17
|
+
|
|
18
|
+
const client = Minimax.fromEnv(); // uses MINIMAX_API_KEY
|
|
19
|
+
|
|
20
|
+
// Generate video
|
|
21
|
+
const video = await client.video.generateAndWait('A cat playing piano');
|
|
22
|
+
|
|
23
|
+
// Generate music
|
|
24
|
+
const music = await client.music.generateAndWait('Upbeat jazz track');
|
|
25
|
+
|
|
26
|
+
// Generate speech
|
|
27
|
+
const audioBuffer = await client.tts.generateToBuffer('Hello world');
|
|
28
|
+
|
|
29
|
+
// Generate image
|
|
30
|
+
const image = await client.image.generateAndWait('A sunset over mountains');
|
|
31
|
+
|
|
32
|
+
// Generate sound effect
|
|
33
|
+
const sfx = await client.soundEffects.generateAndWait('Thunder rolling');
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Environment Variables
|
|
37
|
+
|
|
38
|
+
- `MINIMAX_API_KEY` — API key (required)
|
|
39
|
+
- `MINIMAX_GROUP_ID` — Group ID (optional, some endpoints)
|
|
40
|
+
|
|
41
|
+
## Development
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
bun run dev # Run CLI
|
|
45
|
+
bun run build # Build dist + bin
|
|
46
|
+
bun run typecheck # Type check
|
|
47
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hasna/connect-minimax",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Minimax API connector - Video, music, image, TTS, and sound effects generation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"connect-minimax": "./bin/index.js"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target bun && bun build ./src/cli/index.ts --outdir ./bin --target bun",
|
|
19
|
+
"dev": "bun run ./src/cli/index.ts",
|
|
20
|
+
"typecheck": "tsc --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"chalk": "^5.3.0",
|
|
24
|
+
"commander": "^12.0.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/bun": "latest",
|
|
28
|
+
"typescript": "^5.7.0"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public",
|
|
32
|
+
"registry": "https://registry.npmjs.org/"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"src",
|
|
36
|
+
"dist",
|
|
37
|
+
"bin",
|
|
38
|
+
"tsconfig.json"
|
|
39
|
+
],
|
|
40
|
+
"license": "Apache-2.0"
|
|
41
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { MinimaxConfig, OutputFormat } from '../types';
|
|
2
|
+
import { MinimaxApiError } from '../types';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_BASE_URL = 'https://api.minimax.chat/v1';
|
|
5
|
+
|
|
6
|
+
export interface RequestOptions {
|
|
7
|
+
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
8
|
+
params?: Record<string, string | number | boolean | undefined>;
|
|
9
|
+
body?: Record<string, unknown> | unknown[] | string;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
format?: OutputFormat;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class MinimaxClient {
|
|
15
|
+
private readonly apiKey: string;
|
|
16
|
+
private readonly baseUrl: string;
|
|
17
|
+
private readonly groupId?: string;
|
|
18
|
+
|
|
19
|
+
constructor(config: MinimaxConfig) {
|
|
20
|
+
if (!config.apiKey) {
|
|
21
|
+
throw new Error('API key is required');
|
|
22
|
+
}
|
|
23
|
+
this.apiKey = config.apiKey;
|
|
24
|
+
this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
|
|
25
|
+
this.groupId = config.groupId;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private buildUrl(path: string, params?: Record<string, string | number | boolean | undefined>): string {
|
|
29
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
30
|
+
|
|
31
|
+
if (params) {
|
|
32
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
33
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
34
|
+
url.searchParams.append(key, String(value));
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return url.toString();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async request<T>(path: string, options: RequestOptions = {}): Promise<T> {
|
|
43
|
+
const { method = 'GET', params, body, headers = {} } = options;
|
|
44
|
+
|
|
45
|
+
const url = this.buildUrl(path, params);
|
|
46
|
+
|
|
47
|
+
const requestHeaders: Record<string, string> = {
|
|
48
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
49
|
+
'Accept': 'application/json',
|
|
50
|
+
...headers,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
if (this.groupId) {
|
|
54
|
+
requestHeaders['X-Group-Id'] = this.groupId;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (body && ['POST', 'PUT'].includes(method)) {
|
|
58
|
+
requestHeaders['Content-Type'] = 'application/json';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const fetchOptions: RequestInit = {
|
|
62
|
+
method,
|
|
63
|
+
headers: requestHeaders,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
if (body && ['POST', 'PUT'].includes(method)) {
|
|
67
|
+
fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const response = await fetch(url, fetchOptions);
|
|
71
|
+
|
|
72
|
+
if (response.status === 204) {
|
|
73
|
+
return {} as T;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let data: unknown;
|
|
77
|
+
const contentType = response.headers.get('content-type') || '';
|
|
78
|
+
|
|
79
|
+
if (contentType.includes('application/json')) {
|
|
80
|
+
const text = await response.text();
|
|
81
|
+
if (text) {
|
|
82
|
+
try {
|
|
83
|
+
data = JSON.parse(text);
|
|
84
|
+
} catch {
|
|
85
|
+
data = text;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
data = await response.text();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
const errorData = data as { base_resp?: { status_code: number; status_msg: string } } | undefined;
|
|
94
|
+
const errorMessage = errorData?.base_resp?.status_msg || response.statusText;
|
|
95
|
+
throw new MinimaxApiError(errorMessage, response.status, errorData?.base_resp);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return data as T;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async get<T>(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<T> {
|
|
102
|
+
return this.request<T>(path, { method: 'GET', params });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async post<T>(path: string, body?: Record<string, unknown> | unknown[] | string | object, params?: Record<string, string | number | boolean | undefined>): Promise<T> {
|
|
106
|
+
return this.request<T>(path, { method: 'POST', body: body as Record<string, unknown>, params });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async downloadFile(url: string): Promise<Buffer> {
|
|
110
|
+
const response = await fetch(url);
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
throw new MinimaxApiError(`Download failed: ${response.statusText}`, response.status);
|
|
113
|
+
}
|
|
114
|
+
return Buffer.from(await response.arrayBuffer());
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
getApiKeyPreview(): string {
|
|
118
|
+
if (this.apiKey.length > 10) {
|
|
119
|
+
return `${this.apiKey.substring(0, 6)}...${this.apiKey.substring(this.apiKey.length - 4)}`;
|
|
120
|
+
}
|
|
121
|
+
return '***';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { MinimaxClient } from './client';
|
|
2
|
+
import type {
|
|
3
|
+
ImageModel,
|
|
4
|
+
ImageGenerateRequest,
|
|
5
|
+
ImageGenerateResponse,
|
|
6
|
+
ImageStatusResponse,
|
|
7
|
+
} from '../types';
|
|
8
|
+
|
|
9
|
+
export interface ImageOptions {
|
|
10
|
+
model?: ImageModel;
|
|
11
|
+
aspectRatio?: '1:1' | '16:9' | '9:16' | '4:3' | '3:4';
|
|
12
|
+
n?: number;
|
|
13
|
+
promptOptimizer?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class ImageApi {
|
|
17
|
+
constructor(private readonly client: MinimaxClient) {}
|
|
18
|
+
|
|
19
|
+
async generate(prompt: string, options: ImageOptions = {}): Promise<ImageGenerateResponse> {
|
|
20
|
+
const request: ImageGenerateRequest = {
|
|
21
|
+
model: options.model || 'image-01',
|
|
22
|
+
prompt,
|
|
23
|
+
aspect_ratio: options.aspectRatio || '1:1',
|
|
24
|
+
n: options.n || 1,
|
|
25
|
+
prompt_optimizer: options.promptOptimizer ?? true,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return this.client.post<ImageGenerateResponse>('/image_generation', request);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async getStatus(taskId: string): Promise<ImageStatusResponse> {
|
|
32
|
+
return this.client.get<ImageStatusResponse>('/query/image_generation', { task_id: taskId });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async download(fileId: string): Promise<Buffer> {
|
|
36
|
+
const fileResponse = await this.client.get<{ file: { download_url: string } }>('/files/retrieve', { file_id: fileId });
|
|
37
|
+
return this.client.downloadFile(fileResponse.file.download_url);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async generateAndWait(
|
|
41
|
+
prompt: string,
|
|
42
|
+
options: ImageOptions = {},
|
|
43
|
+
pollIntervalMs = 3000,
|
|
44
|
+
maxAttempts = 60
|
|
45
|
+
): Promise<{ fileId: string; downloadUrl: string }> {
|
|
46
|
+
const job = await this.generate(prompt, options);
|
|
47
|
+
const taskId = job.task_id;
|
|
48
|
+
|
|
49
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
50
|
+
await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
|
|
51
|
+
const status = await this.getStatus(taskId);
|
|
52
|
+
|
|
53
|
+
if (status.status === 'Success' && status.file_id) {
|
|
54
|
+
const fileResponse = await this.client.get<{ file: { download_url: string } }>('/files/retrieve', { file_id: status.file_id });
|
|
55
|
+
return { fileId: status.file_id, downloadUrl: fileResponse.file.download_url };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (status.status === 'Fail') {
|
|
59
|
+
throw new Error(`Image generation failed: ${status.base_resp?.status_msg || 'Unknown error'}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
throw new Error('Image generation timed out');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { MinimaxConfig } from '../types';
|
|
2
|
+
import { MinimaxClient } from './client';
|
|
3
|
+
import { VideoApi } from './video';
|
|
4
|
+
import { MusicApi } from './music';
|
|
5
|
+
import { TTSApi } from './tts';
|
|
6
|
+
import { ImageApi } from './image';
|
|
7
|
+
import { SoundEffectsApi } from './sound-effects';
|
|
8
|
+
|
|
9
|
+
export class Minimax {
|
|
10
|
+
private readonly client: MinimaxClient;
|
|
11
|
+
|
|
12
|
+
public readonly video: VideoApi;
|
|
13
|
+
public readonly music: MusicApi;
|
|
14
|
+
public readonly tts: TTSApi;
|
|
15
|
+
public readonly image: ImageApi;
|
|
16
|
+
public readonly soundEffects: SoundEffectsApi;
|
|
17
|
+
|
|
18
|
+
constructor(config: MinimaxConfig) {
|
|
19
|
+
this.client = new MinimaxClient(config);
|
|
20
|
+
this.video = new VideoApi(this.client);
|
|
21
|
+
this.music = new MusicApi(this.client);
|
|
22
|
+
this.tts = new TTSApi(this.client);
|
|
23
|
+
this.image = new ImageApi(this.client);
|
|
24
|
+
this.soundEffects = new SoundEffectsApi(this.client);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static fromEnv(): Minimax {
|
|
28
|
+
const apiKey = process.env.MINIMAX_API_KEY;
|
|
29
|
+
const groupId = process.env.MINIMAX_GROUP_ID;
|
|
30
|
+
|
|
31
|
+
if (!apiKey) {
|
|
32
|
+
throw new Error('MINIMAX_API_KEY environment variable is required');
|
|
33
|
+
}
|
|
34
|
+
return new Minimax({ apiKey, groupId });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getApiKeyPreview(): string {
|
|
38
|
+
return this.client.getApiKeyPreview();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getClient(): MinimaxClient {
|
|
42
|
+
return this.client;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const Connector = Minimax;
|
|
47
|
+
|
|
48
|
+
export { MinimaxClient } from './client';
|
|
49
|
+
export { VideoApi } from './video';
|
|
50
|
+
export { MusicApi } from './music';
|
|
51
|
+
export { TTSApi } from './tts';
|
|
52
|
+
export { ImageApi } from './image';
|
|
53
|
+
export { SoundEffectsApi } from './sound-effects';
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { MinimaxClient } from './client';
|
|
2
|
+
import type {
|
|
3
|
+
MusicModel,
|
|
4
|
+
MusicGenerateRequest,
|
|
5
|
+
MusicGenerateResponse,
|
|
6
|
+
MusicStatusResponse,
|
|
7
|
+
} from '../types';
|
|
8
|
+
|
|
9
|
+
export interface MusicOptions {
|
|
10
|
+
model?: MusicModel;
|
|
11
|
+
lyrics?: string;
|
|
12
|
+
referVoice?: string;
|
|
13
|
+
referInstrumental?: string;
|
|
14
|
+
genre?: string;
|
|
15
|
+
mood?: string;
|
|
16
|
+
tempo?: number;
|
|
17
|
+
duration?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class MusicApi {
|
|
21
|
+
constructor(private readonly client: MinimaxClient) {}
|
|
22
|
+
|
|
23
|
+
async generate(prompt: string, options: MusicOptions = {}): Promise<MusicGenerateResponse> {
|
|
24
|
+
const request: MusicGenerateRequest = {
|
|
25
|
+
model: options.model || 'music-01',
|
|
26
|
+
prompt,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
if (options.lyrics) request.lyrics = options.lyrics;
|
|
30
|
+
if (options.referVoice) request.refer_voice = options.referVoice;
|
|
31
|
+
if (options.referInstrumental) request.refer_instrumental = options.referInstrumental;
|
|
32
|
+
if (options.genre) request.genre = options.genre;
|
|
33
|
+
if (options.mood) request.mood = options.mood;
|
|
34
|
+
if (options.tempo) request.tempo = options.tempo;
|
|
35
|
+
if (options.duration) request.duration = options.duration;
|
|
36
|
+
|
|
37
|
+
return this.client.post<MusicGenerateResponse>('/music_generation', request);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async getStatus(taskId: string): Promise<MusicStatusResponse> {
|
|
41
|
+
return this.client.get<MusicStatusResponse>('/query/music_generation', { task_id: taskId });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async download(audioUrl: string): Promise<Buffer> {
|
|
45
|
+
return this.client.downloadFile(audioUrl);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async generateAndWait(
|
|
49
|
+
prompt: string,
|
|
50
|
+
options: MusicOptions = {},
|
|
51
|
+
pollIntervalMs = 5000,
|
|
52
|
+
maxAttempts = 120
|
|
53
|
+
): Promise<{ audioUrl: string; lyrics?: string; instrumentalUrl?: string }> {
|
|
54
|
+
const job = await this.generate(prompt, options);
|
|
55
|
+
const taskId = job.task_id;
|
|
56
|
+
|
|
57
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
58
|
+
await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
|
|
59
|
+
const status = await this.getStatus(taskId);
|
|
60
|
+
|
|
61
|
+
if (status.status === 'Success') {
|
|
62
|
+
const audioUrl = status.extra_info?.audio_url || status.audio_file;
|
|
63
|
+
if (!audioUrl) throw new Error('No audio URL in completed response');
|
|
64
|
+
return {
|
|
65
|
+
audioUrl,
|
|
66
|
+
lyrics: status.extra_info?.lyrics,
|
|
67
|
+
instrumentalUrl: status.extra_info?.instrumental_url,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (status.status === 'Fail') {
|
|
72
|
+
throw new Error(`Music generation failed: ${status.base_resp?.status_msg || 'Unknown error'}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
throw new Error('Music generation timed out');
|
|
77
|
+
}
|
|
78
|
+
}
|