@hasna/connectors 1.3.19 → 1.3.21

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 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.19",
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",
@@ -33023,7 +33030,7 @@ function redactSecrets(obj) {
33023
33030
  return obj;
33024
33031
  }
33025
33032
  function registerCommands4(program2) {
33026
- program2.command("auth").argument("<connector>", "Connector name to configure auth for").option("-k, --key <value>", "API key or bearer token value (non-interactive)").option("-f, --field <field>", "Which field to set (for multi-field connectors)").option("--json", "Output as JSON", false).description("Configure authentication for a connector").action(async (connector, options) => {
33033
+ program2.command("auth").argument("<connector>", "Connector name to configure auth for").option("-k, --key <value>", "API key or bearer token value (non-interactive)").option("-f, --field <field>", "Which field to set (for multi-field connectors)").option("--json", "Output as JSON", false).option("--no-browser", "Print OAuth URL without opening a browser (agent-friendly)", false).description("Configure authentication for a connector").action(async (connector, options) => {
33027
33034
  const meta = getConnector(connector);
33028
33035
  if (!meta) {
33029
33036
  if (options.json) {
@@ -33062,12 +33069,65 @@ ${meta.displayName} \u2014 Auth Configuration
33062
33069
  }
33063
33070
  console.log(chalk5.yellow("OAuth connectors require browser-based authentication."));
33064
33071
  console.log();
33072
+ const port = 9876;
33073
+ const oauthUrl = `http://localhost:${port}/oauth/${connector}/start`;
33065
33074
  try {
33066
- const port = 9876;
33075
+ if (!options.browser) {
33076
+ console.log(chalk5.dim(`Starting OAuth server on port ${port}...`));
33077
+ const { spawn: spawn4 } = await import("child_process");
33078
+ const scriptPath = process.argv[1];
33079
+ const serverProc = spawn4("node", [scriptPath, "serve", "--port", String(port)], {
33080
+ detached: true,
33081
+ stdio: "ignore"
33082
+ });
33083
+ serverProc.unref();
33084
+ await new Promise((resolve) => setTimeout(resolve, 2000));
33085
+ try {
33086
+ await fetch(`http://localhost:${port}/api/connectors`);
33087
+ } catch {
33088
+ console.log(chalk5.red(`OAuth server failed to start on port ${port}. Is the port already in use?`));
33089
+ console.log(chalk5.dim("Free the port and try again, or use 'connectors serve' for the full dashboard."));
33090
+ process.exit(1);
33091
+ return;
33092
+ }
33093
+ console.log(chalk5.bold(`Open this URL to authenticate:
33094
+ `));
33095
+ console.log(` ${chalk5.cyan(oauthUrl)}
33096
+ `);
33097
+ console.log(chalk5.dim(`Waiting for authentication to complete...
33098
+ `));
33099
+ const { join: join8 } = await import("path");
33100
+ const { getConnectorsHome: getConnectorsHome2 } = await Promise.resolve().then(() => (init_database(), exports_database));
33101
+ const connectorsHome = getConnectorsHome2();
33102
+ const connectorDirName = connector.startsWith("connect-") ? connector : `connect-${connector}`;
33103
+ const tokensPath = join8(connectorsHome, connectorDirName, "profiles", "default", "tokens.json");
33104
+ let attempts = 0;
33105
+ const maxAttempts = 360;
33106
+ while (attempts < maxAttempts) {
33107
+ await new Promise((resolve) => setTimeout(resolve, 500));
33108
+ if (existsSync19(tokensPath)) {
33109
+ break;
33110
+ }
33111
+ attempts++;
33112
+ }
33113
+ try {
33114
+ serverProc.kill("SIGTERM");
33115
+ } catch {}
33116
+ if (attempts >= maxAttempts) {
33117
+ console.log(chalk5.yellow("Timed out waiting for OAuth callback. The auth may still be in progress."));
33118
+ console.log(chalk5.dim(`Check ${tokensPath} for tokens.`));
33119
+ console.log(chalk5.dim("You can stop the server manually: lsof -ti :${port} | xargs kill"));
33120
+ process.exit(1);
33121
+ return;
33122
+ }
33123
+ console.log(chalk5.green(`
33124
+ \u2713 Connected! ${meta.displayName} is now authenticated.`));
33125
+ process.exit(0);
33126
+ return;
33127
+ }
33067
33128
  const { startServer: startServer2 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
33068
33129
  console.log(chalk5.dim(`Starting temporary server on port ${port}...`));
33069
33130
  await startServer2(port, { open: false, strict: true });
33070
- const oauthUrl = `http://localhost:${port}/oauth/${connector}/start`;
33071
33131
  console.log(chalk5.bold(`
33072
33132
  Open this URL to authenticate:
33073
33133
  `));
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.19",
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.19",
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",
@@ -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
+ }
@@ -0,0 +1,59 @@
1
+ import type { MinimaxClient } from './client';
2
+ import type {
3
+ SoundEffectRequest,
4
+ SoundEffectResponse,
5
+ SoundEffectStatusResponse,
6
+ } from '../types';
7
+
8
+ export interface SoundEffectOptions {
9
+ duration?: number;
10
+ }
11
+
12
+ export class SoundEffectsApi {
13
+ constructor(private readonly client: MinimaxClient) {}
14
+
15
+ async generate(prompt: string, options: SoundEffectOptions = {}): Promise<SoundEffectResponse> {
16
+ const request: SoundEffectRequest = {
17
+ model: 'sound-effects-01',
18
+ prompt,
19
+ duration: options.duration,
20
+ };
21
+
22
+ return this.client.post<SoundEffectResponse>('/sound_generation', request);
23
+ }
24
+
25
+ async getStatus(taskId: string): Promise<SoundEffectStatusResponse> {
26
+ return this.client.get<SoundEffectStatusResponse>('/query/sound_generation', { task_id: taskId });
27
+ }
28
+
29
+ async download(audioUrl: string): Promise<Buffer> {
30
+ return this.client.downloadFile(audioUrl);
31
+ }
32
+
33
+ async generateAndWait(
34
+ prompt: string,
35
+ options: SoundEffectOptions = {},
36
+ pollIntervalMs = 3000,
37
+ maxAttempts = 60
38
+ ): Promise<{ audioUrl: string }> {
39
+ const job = await this.generate(prompt, options);
40
+ const taskId = job.task_id;
41
+
42
+ for (let i = 0; i < maxAttempts; i++) {
43
+ await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
44
+ const status = await this.getStatus(taskId);
45
+
46
+ if (status.status === 'Success') {
47
+ const audioUrl = status.extra_info?.audio_url || status.audio_file;
48
+ if (!audioUrl) throw new Error('No audio URL in completed response');
49
+ return { audioUrl };
50
+ }
51
+
52
+ if (status.status === 'Fail') {
53
+ throw new Error(`Sound effect generation failed: ${status.base_resp?.status_msg || 'Unknown error'}`);
54
+ }
55
+ }
56
+
57
+ throw new Error('Sound effect generation timed out');
58
+ }
59
+ }
@@ -0,0 +1,56 @@
1
+ import type { MinimaxClient } from './client';
2
+ import type { TTSModel, TTSRequest, TTSResponse } from '../types';
3
+
4
+ export interface TTSOptions {
5
+ model?: TTSModel;
6
+ voiceId?: string;
7
+ speed?: number;
8
+ volume?: number;
9
+ pitch?: number;
10
+ emotion?: string;
11
+ format?: 'mp3' | 'wav' | 'pcm' | 'flac';
12
+ sampleRate?: number;
13
+ languageBoost?: string;
14
+ }
15
+
16
+ export class TTSApi {
17
+ constructor(private readonly client: MinimaxClient) {}
18
+
19
+ async generate(text: string, options: TTSOptions = {}): Promise<TTSResponse> {
20
+ const request: TTSRequest = {
21
+ model: options.model || 'speech-02-hd',
22
+ text,
23
+ };
24
+
25
+ if (options.voiceId || options.speed || options.volume || options.pitch || options.emotion) {
26
+ request.voice_setting = {};
27
+ if (options.voiceId) request.voice_setting.voice_id = options.voiceId;
28
+ if (options.speed) request.voice_setting.speed = options.speed;
29
+ if (options.volume) request.voice_setting.vol = options.volume;
30
+ if (options.pitch) request.voice_setting.pitch = options.pitch;
31
+ if (options.emotion) request.voice_setting.emotion = options.emotion;
32
+ }
33
+
34
+ if (options.format || options.sampleRate) {
35
+ request.audio_setting = {};
36
+ if (options.format) request.audio_setting.format = options.format;
37
+ if (options.sampleRate) request.audio_setting.sample_rate = options.sampleRate;
38
+ }
39
+
40
+ if (options.languageBoost) {
41
+ request.language_boost = options.languageBoost;
42
+ }
43
+
44
+ return this.client.post<TTSResponse>('/t2a_v2', request);
45
+ }
46
+
47
+ async generateToBuffer(text: string, options: TTSOptions = {}): Promise<Buffer> {
48
+ const response = await this.generate(text, options);
49
+
50
+ if (!response.data?.audio) {
51
+ throw new Error('No audio data in TTS response');
52
+ }
53
+
54
+ return Buffer.from(response.data.audio, 'hex');
55
+ }
56
+ }