openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
312 lines (311 loc) • 12.8 kB
JavaScript
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import { n as readResponseWithLimit } from "./read-response-with-limit-MDCSJrlg.js";
import { r as extensionForMime } from "./mime-C8mVE2Bw.js";
import { r as assertOkOrThrowHttpError } from "./provider-http-errors-DqaqQLLZ.js";
import { c as postJsonRequest, i as fetchProviderDownloadResponse, m as resolveProviderOperationTimeoutMs, n as createProviderOperationDeadline, p as resolveProviderHttpRequestConfig } from "./shared-IuCmx8oC.js";
import "./string-coerce-runtime-CEGJWkQ_.js";
import { r as isProviderApiKeyConfigured } from "./provider-auth-DAOC_qI9.js";
import "./media-mime-v9DSctG7.js";
import "./response-limit-runtime-B0ZHF0eR.js";
import { a as resolveApiKeyForProvider } from "./provider-auth-runtime-D2xYnWtb.js";
import "./provider-http-BXCBCi4E.js";
//#region extensions/minimax/music-generation-provider.ts
const DEFAULT_MINIMAX_MUSIC_BASE_URL = "https://api.minimax.io";
const DEFAULT_MINIMAX_MUSIC_MODEL = "music-2.6";
const DEFAULT_TIMEOUT_MS = 12e4;
const DEFAULT_OPERATION_TIMEOUT_MS = 3e5;
const DEFAULT_GENERATED_MUSIC_MAX_BYTES = 16 * 1024 * 1024;
const STREAM_ENVELOPE_MAX_BYTES_MULTIPLIER = 5;
const STREAM_ENVELOPE_OVERHEAD_BYTES = 64 * 1024;
function resolveMinimaxMusicBaseUrl(cfg, providerId) {
const direct = normalizeOptionalString(cfg?.models?.providers?.[providerId]?.baseUrl);
if (!direct) return DEFAULT_MINIMAX_MUSIC_BASE_URL;
try {
return new URL(direct).origin;
} catch {
return DEFAULT_MINIMAX_MUSIC_BASE_URL;
}
}
function assertMinimaxBaseResp(baseResp, context) {
if (!baseResp || typeof baseResp.status_code !== "number" || baseResp.status_code === 0) return;
throw new Error(`${context} (${baseResp.status_code}): ${baseResp.status_msg ?? "unknown error"}`);
}
function decodePossibleBinaryWithLimit(data, maxBytes) {
const trimmed = data.trim();
if (/^[0-9a-f]+$/iu.test(trimmed) && trimmed.length % 2 === 0) {
if (trimmed.length / 2 > maxBytes) throw createGeneratedMusicTooLargeError(maxBytes);
return Buffer.from(trimmed, "hex");
}
if (Buffer.byteLength(trimmed, "base64") > maxBytes) throw createGeneratedMusicTooLargeError(maxBytes);
return Buffer.from(trimmed, "base64");
}
function decodePossibleText(data) {
const trimmed = data.trim();
if (!trimmed) return "";
if (/^[0-9a-f]+$/iu.test(trimmed) && trimmed.length % 2 === 0) return Buffer.from(trimmed, "hex").toString("utf8").trim();
return trimmed;
}
function isLikelyRemoteUrl(value) {
const trimmed = normalizeOptionalString(value);
return Boolean(trimmed && /^https?:\/\//iu.test(trimmed));
}
function resolveGeneratedMusicMaxBytes(req) {
const configured = req.cfg.agents?.defaults?.mediaMaxMb;
if (typeof configured === "number" && Number.isFinite(configured) && configured > 0) return Math.floor(configured * 1024 * 1024);
return DEFAULT_GENERATED_MUSIC_MAX_BYTES;
}
async function downloadTrackFromUrl(params) {
const response = await fetchProviderDownloadResponse({
url: params.url,
init: { method: "GET" },
timeoutMs: params.timeoutMs ?? DEFAULT_TIMEOUT_MS,
fetchFn: params.fetchFn,
provider: "minimax",
requestFailedMessage: "MiniMax generated music download failed"
});
const mimeType = normalizeOptionalString(response.headers.get("content-type")) ?? "audio/mpeg";
const ext = extensionForMime(mimeType)?.replace(/^\./u, "") || "mp3";
return {
buffer: await readResponseWithLimit(response, params.maxBytes, { onOverflow: ({ maxBytes }) => /* @__PURE__ */ new Error(`MiniMax generated music download exceeds ${maxBytes} bytes`) }),
mimeType,
fileName: `track-1.${ext}`
};
}
function createMinimaxMusicTimeoutError(deadline) {
const timeoutLabel = typeof deadline.timeoutMs === "number" ? ` after ${deadline.timeoutMs}ms` : "";
return /* @__PURE__ */ new Error(`${deadline.label} timed out${timeoutLabel}`);
}
function resolveBodyReadTimeoutMs(deadline) {
return resolveProviderOperationTimeoutMs({
deadline,
defaultTimeoutMs: deadline.timeoutMs ?? DEFAULT_OPERATION_TIMEOUT_MS
});
}
function createGeneratedMusicTooLargeError(maxBytes) {
return /* @__PURE__ */ new Error(`MiniMax generated music download exceeds ${maxBytes} bytes`);
}
function resolveStreamEnvelopeMaxBytes(maxBytes) {
return Math.max(STREAM_ENVELOPE_OVERHEAD_BYTES, maxBytes * STREAM_ENVELOPE_MAX_BYTES_MULTIPLIER + STREAM_ENVELOPE_OVERHEAD_BYTES);
}
async function readResponseBufferWithDeadline(response, deadline, maxBytes) {
const body = response.body;
if (!body) return Buffer.alloc(0);
const reader = body.getReader();
const chunks = [];
let totalBytes = 0;
try {
while (true) {
let timeoutId;
try {
const timeoutMs = resolveBodyReadTimeoutMs(deadline);
const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(() => reject(createMinimaxMusicTimeoutError(deadline)), timeoutMs);
});
const result = await Promise.race([reader.read(), timeoutPromise]);
if (result.done) break;
if (!result.value || result.value.length === 0) continue;
const nextTotalBytes = totalBytes + result.value.byteLength;
if (nextTotalBytes > maxBytes) {
const error = createGeneratedMusicTooLargeError(maxBytes);
try {
await reader.cancel(error);
} catch {}
throw error;
}
chunks.push(result.value);
totalBytes = nextTotalBytes;
} catch (error) {
try {
await reader.cancel(error);
} catch {}
throw error;
} finally {
if (timeoutId) clearTimeout(timeoutId);
}
}
} finally {
reader.releaseLock();
}
const buffer = Buffer.allocUnsafe(totalBytes);
let offset = 0;
for (const chunk of chunks) {
buffer.set(chunk, offset);
offset += chunk.byteLength;
}
return buffer;
}
async function readStreamingTrack(response, deadline, maxBytes) {
const contentType = normalizeOptionalString(response.headers.get("content-type")) ?? "";
if (contentType.toLowerCase().startsWith("audio/")) {
const ext = extensionForMime(contentType)?.replace(/^\./u, "") || "mp3";
return {
buffer: await readResponseBufferWithDeadline(response, deadline, maxBytes),
mimeType: contentType,
fileName: `track-1.${ext}`
};
}
const chunks = [];
let decodedBytes = 0;
const text = new TextDecoder().decode(await readResponseBufferWithDeadline(response, deadline, resolveStreamEnvelopeMaxBytes(maxBytes)));
for (const rawLine of text.split(/\r?\n/u)) {
const line = rawLine.trim();
if (!line.startsWith("data:")) continue;
const json = line.slice(5).trim();
if (!json || json === "[DONE]") continue;
const frame = JSON.parse(json);
assertMinimaxBaseResp(frame.base_resp, "MiniMax music generation failed");
const audio = normalizeOptionalString(frame.data?.audio);
if (audio) {
if (String(frame.data?.status ?? "") === "2" && chunks.length > 0) continue;
const chunk = decodePossibleBinaryWithLimit(audio, maxBytes - decodedBytes);
const nextDecodedBytes = decodedBytes + chunk.byteLength;
if (nextDecodedBytes > maxBytes) throw createGeneratedMusicTooLargeError(maxBytes);
chunks.push(chunk);
decodedBytes = nextDecodedBytes;
}
}
const buffer = Buffer.concat(chunks);
if (buffer.byteLength === 0) throw new Error("MiniMax music generation response missing audio output");
return {
buffer,
mimeType: "audio/mpeg",
fileName: "track-1.mp3"
};
}
function resolveMinimaxMusicModel(model) {
const trimmed = normalizeOptionalString(model);
if (!trimmed) return DEFAULT_MINIMAX_MUSIC_MODEL;
return trimmed;
}
function buildMinimaxMusicProvider(providerId) {
return {
id: providerId,
label: "MiniMax",
defaultModel: DEFAULT_MINIMAX_MUSIC_MODEL,
models: [
DEFAULT_MINIMAX_MUSIC_MODEL,
"music-2.6-free",
"music-cover",
"music-cover-free"
],
isConfigured: ({ agentDir }) => isProviderApiKeyConfigured({
provider: providerId,
agentDir
}),
capabilities: {
generate: {
maxTracks: 1,
supportsLyrics: true,
supportsInstrumental: true,
supportsFormat: true,
supportedFormats: ["mp3"]
},
edit: { enabled: false }
},
async generateMusic(req) {
if ((req.inputImages?.length ?? 0) > 0) throw new Error("MiniMax music generation does not support image reference inputs.");
if (req.instrumental === true && normalizeOptionalString(req.lyrics)) throw new Error("MiniMax music generation cannot use lyrics when instrumental=true.");
if (req.format && req.format !== "mp3") throw new Error("MiniMax music generation currently supports mp3 output only.");
const auth = await resolveApiKeyForProvider({
provider: providerId,
cfg: req.cfg,
agentDir: req.agentDir,
store: req.authStore
});
if (!auth.apiKey) throw new Error("MiniMax API key missing");
const fetchFn = fetch;
const operationTimeoutMs = req.timeoutMs ?? DEFAULT_OPERATION_TIMEOUT_MS;
const deadline = createProviderOperationDeadline({
timeoutMs: operationTimeoutMs,
label: "MiniMax music generation"
});
const { baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } = resolveProviderHttpRequestConfig({
baseUrl: resolveMinimaxMusicBaseUrl(req.cfg, providerId),
defaultBaseUrl: DEFAULT_MINIMAX_MUSIC_BASE_URL,
allowPrivateNetwork: false,
defaultHeaders: { Authorization: `Bearer ${auth.apiKey}` },
provider: providerId,
capability: "audio",
transport: "http"
});
const jsonHeaders = new Headers(headers);
jsonHeaders.set("Content-Type", "application/json");
const model = resolveMinimaxMusicModel(req.model);
const requestedLyrics = normalizeOptionalString(req.lyrics);
const body = {
model,
prompt: req.prompt.trim(),
...req.instrumental === true ? { is_instrumental: true } : {},
...requestedLyrics ? { lyrics: requestedLyrics } : req.instrumental === true ? {} : { lyrics_optimizer: true },
stream: true,
output_format: "hex",
audio_setting: {
sample_rate: 44100,
bitrate: 256e3,
format: "mp3"
}
};
const { response: res, release } = await postJsonRequest({
url: `${baseUrl}/v1/music_generation`,
headers: jsonHeaders,
body,
timeoutMs: resolveProviderOperationTimeoutMs({
deadline,
defaultTimeoutMs: operationTimeoutMs
}),
fetchFn,
pinDns: false,
allowPrivateNetwork,
dispatcherPolicy
});
try {
await assertOkOrThrowHttpError(res, "MiniMax music generation failed");
const lowerContentType = (normalizeOptionalString(res.headers.get("content-type")) ?? "").toLowerCase();
const maxGeneratedMusicBytes = resolveGeneratedMusicMaxBytes(req);
const payload = lowerContentType.includes("text/event-stream") || lowerContentType.startsWith("audio/") ? null : JSON.parse(new TextDecoder().decode(await readResponseBufferWithDeadline(res.clone(), deadline, resolveStreamEnvelopeMaxBytes(maxGeneratedMusicBytes))));
if (payload) assertMinimaxBaseResp(payload.base_resp, "MiniMax music generation failed");
const audioCandidate = normalizeOptionalString(payload?.audio) ?? normalizeOptionalString(payload?.data?.audio);
const audioUrl = normalizeOptionalString(payload?.audio_url) || normalizeOptionalString(payload?.data?.audio_url) || (isLikelyRemoteUrl(audioCandidate) ? audioCandidate : void 0);
const inlineAudio = isLikelyRemoteUrl(audioCandidate) ? void 0 : audioCandidate;
const responseLyrics = decodePossibleText(payload?.lyrics ?? payload?.data?.lyrics ?? "");
const track = audioUrl ? await downloadTrackFromUrl({
url: audioUrl,
timeoutMs: resolveProviderOperationTimeoutMs({
deadline,
defaultTimeoutMs: req.timeoutMs ?? DEFAULT_TIMEOUT_MS
}),
fetchFn,
maxBytes: resolveGeneratedMusicMaxBytes(req)
}) : inlineAudio ? (() => {
return {
buffer: decodePossibleBinaryWithLimit(inlineAudio, maxGeneratedMusicBytes),
mimeType: "audio/mpeg",
fileName: "track-1.mp3"
};
})() : await readStreamingTrack(res, deadline, maxGeneratedMusicBytes);
if (!track) throw new Error("MiniMax music generation response missing audio output");
return {
tracks: [track],
...responseLyrics ? { lyrics: [responseLyrics] } : {},
model,
metadata: {
...normalizeOptionalString(payload?.task_id) ? { taskId: normalizeOptionalString(payload?.task_id) } : {},
...audioUrl ? { audioUrl } : {},
instrumental: req.instrumental === true,
...requestedLyrics ? { requestedLyrics: true } : {}
}
};
} finally {
await release();
}
}
};
}
function buildMinimaxMusicGenerationProvider() {
return buildMinimaxMusicProvider("minimax");
}
function buildMinimaxPortalMusicGenerationProvider() {
return buildMinimaxMusicProvider("minimax-portal");
}
//#endregion
export { buildMinimaxPortalMusicGenerationProvider as n, buildMinimaxMusicGenerationProvider as t };