openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
237 lines (236 loc) • 8.67 kB
JavaScript
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import { A as resolvePositiveTimerTimeoutMs } from "./number-coercion-CJQ8TR--.js";
import { c as isRecord } from "./utils-CCC-BEJH.js";
import { r as assertOkOrThrowHttpError } from "./provider-http-errors-DqaqQLLZ.js";
import { c as postJsonRequest, m as resolveProviderOperationTimeoutMs, n as createProviderOperationDeadline, p as resolveProviderHttpRequestConfig } from "./shared-IuCmx8oC.js";
import "./number-runtime-DBLVDypr.js";
import "./string-coerce-runtime-CEGJWkQ_.js";
import { r as isProviderApiKeyConfigured } from "./provider-auth-DAOC_qI9.js";
import { a as resolveApiKeyForProvider } from "./provider-auth-runtime-D2xYnWtb.js";
import "./provider-http-BXCBCi4E.js";
import { t as OPENROUTER_BASE_URL } from "./provider-catalog-Dya4CIwt.js";
//#region extensions/openrouter/music-generation-provider.ts
const DEFAULT_OPENROUTER_MUSIC_MODEL = "google/lyria-3-pro-preview";
const OPENROUTER_CLIP_MUSIC_MODEL = "google/lyria-3-clip-preview";
const DEFAULT_TIMEOUT_MS = 18e4;
const OPENROUTER_MUSIC_MODELS = [DEFAULT_OPENROUTER_MUSIC_MODEL, OPENROUTER_CLIP_MUSIC_MODEL];
function resolveOpenRouterMusicModel(model) {
return normalizeOptionalString(model) ?? DEFAULT_OPENROUTER_MUSIC_MODEL;
}
function outputFormatToMimeType(format) {
return format === "mp3" ? "audio/mpeg" : "audio/wav";
}
function imageToContentPart(image) {
const url = normalizeOptionalString(image.url) ?? (image.buffer ? `data:${normalizeOptionalString(image.mimeType) ?? "image/png"};base64,${image.buffer.toString("base64")}` : void 0);
if (!url) throw new Error("OpenRouter music generation reference image is missing data.");
return {
type: "image_url",
image_url: { url }
};
}
function buildOpenRouterMusicPrompt(req) {
const parts = [req.prompt.trim()];
const lyrics = normalizeOptionalString(req.lyrics);
if (req.instrumental === true) parts.push("Instrumental only. No vocals, no sung lyrics, no spoken word.");
if (lyrics) parts.push(`Lyrics:\n${lyrics}`);
if (typeof req.durationSeconds === "number") parts.push(`Target duration: about ${Math.round(req.durationSeconds)} seconds.`);
return parts.join("\n\n");
}
function buildOpenRouterMessageContent(req) {
const prompt = buildOpenRouterMusicPrompt(req);
const images = req.inputImages ?? [];
if (images.length === 0) return prompt;
return [{
type: "text",
text: prompt
}, ...images.map((image) => imageToContentPart(image))];
}
function readDeltaAudio(part) {
if (!isRecord(part)) return;
const choices = part.choices;
if (!Array.isArray(choices)) return;
const first = choices[0];
if (!isRecord(first)) return;
const delta = first.delta;
if (!isRecord(delta)) return;
const audio = delta.audio;
if (!isRecord(audio)) return;
return {
data: normalizeOptionalString(audio.data),
transcript: typeof audio.transcript === "string" ? audio.transcript : void 0
};
}
function processOpenRouterSseLine(line, result) {
if (!line.startsWith("data:")) return false;
const data = line.slice(5).trim();
if (!data) return false;
if (data === "[DONE]") return true;
const audio = readDeltaAudio(JSON.parse(data));
if (audio?.data) result.audioBuffers.push(Buffer.from(audio.data, "base64"));
if (audio?.transcript) result.transcriptChunks.push(audio.transcript);
return false;
}
function resolveOpenRouterStreamRemainingMs(deadline) {
return resolveProviderOperationTimeoutMs({
deadline,
defaultTimeoutMs: deadline.timeoutMs ?? DEFAULT_TIMEOUT_MS
});
}
async function readOpenRouterStreamChunk(reader, deadline) {
const timeoutMs = resolveOpenRouterStreamRemainingMs(deadline);
let timeoutId;
try {
return await Promise.race([reader.read(), new Promise((_, reject) => {
timeoutId = setTimeout(() => {
reject(/* @__PURE__ */ new Error(`${deadline.label} timed out after ${deadline.timeoutMs}ms`));
}, timeoutMs);
})]);
} catch (error) {
await reader.cancel().catch(() => {});
throw error;
} finally {
if (timeoutId) clearTimeout(timeoutId);
}
}
async function readOpenRouterAudioStream(response, deadline) {
if (!response.body) throw new Error("OpenRouter music generation response missing stream body");
const reader = response.body.getReader();
const decoder = new TextDecoder();
const result = {
audioBuffers: [],
transcriptChunks: []
};
let buffer = "";
let doneSeen = false;
for (;;) {
const { value, done } = await readOpenRouterStreamChunk(reader, deadline);
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split(/\r?\n/u);
buffer = lines.pop() ?? "";
for (const line of lines) if (processOpenRouterSseLine(line.trim(), result)) {
await reader.cancel();
return {
audioBuffer: Buffer.concat(result.audioBuffers),
transcript: result.transcriptChunks.join("")
};
}
}
resolveOpenRouterStreamRemainingMs(deadline);
buffer += decoder.decode();
if (buffer.trim()) {
for (const line of buffer.split(/\r?\n/u)) if (processOpenRouterSseLine(line.trim(), result)) doneSeen = true;
}
if (!doneSeen) throw new Error("OpenRouter music generation stream ended before completion");
return {
audioBuffer: Buffer.concat(result.audioBuffers),
transcript: result.transcriptChunks.join("")
};
}
function buildOpenRouterMusicGenerationProvider() {
return {
id: "openrouter",
label: "OpenRouter",
defaultModel: DEFAULT_OPENROUTER_MUSIC_MODEL,
models: [...OPENROUTER_MUSIC_MODELS],
isConfigured: ({ agentDir }) => isProviderApiKeyConfigured({
provider: "openrouter",
agentDir
}),
capabilities: {
generate: {
maxTracks: 1,
maxDurationSeconds: 180,
supportsLyrics: true,
supportsInstrumental: true,
supportsDuration: true,
supportsFormat: true,
supportedFormats: ["mp3", "wav"]
},
edit: {
enabled: true,
maxTracks: 1,
maxInputImages: 1,
maxDurationSeconds: 180,
supportsLyrics: true,
supportsInstrumental: true,
supportsDuration: true,
supportsFormat: true,
supportedFormats: ["mp3", "wav"]
}
},
async generateMusic(req) {
if ((req.inputImages?.length ?? 0) > 1) throw new Error("OpenRouter music generation supports at most one reference image.");
const auth = await resolveApiKeyForProvider({
provider: "openrouter",
cfg: req.cfg,
agentDir: req.agentDir,
store: req.authStore
});
if (!auth.apiKey) throw new Error("OpenRouter API key missing");
const { baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } = resolveProviderHttpRequestConfig({
baseUrl: req.cfg?.models?.providers?.openrouter?.baseUrl,
defaultBaseUrl: OPENROUTER_BASE_URL,
allowPrivateNetwork: false,
defaultHeaders: {
Authorization: `Bearer ${auth.apiKey}`,
"Content-Type": "application/json",
"HTTP-Referer": "https://openclaw.ai",
"X-OpenRouter-Title": "OpenClaw"
},
provider: "openrouter",
capability: "audio",
transport: "http"
});
const model = resolveOpenRouterMusicModel(req.model);
const format = req.format ?? "wav";
const streamDeadline = createProviderOperationDeadline({
timeoutMs: resolvePositiveTimerTimeoutMs(req.timeoutMs, DEFAULT_TIMEOUT_MS),
label: "OpenRouter music generation"
});
const timeoutMs = resolveOpenRouterStreamRemainingMs(streamDeadline);
const { response, release } = await postJsonRequest({
url: `${baseUrl}/chat/completions`,
headers,
body: {
model,
messages: [{
role: "user",
content: buildOpenRouterMessageContent(req)
}],
modalities: ["text", "audio"],
audio: { format },
stream: true
},
timeoutMs,
fetchFn: fetch,
allowPrivateNetwork,
dispatcherPolicy
});
try {
await assertOkOrThrowHttpError(response, "OpenRouter music generation failed");
const streamResult = await readOpenRouterAudioStream(response, streamDeadline);
if (streamResult.audioBuffer.byteLength === 0) throw new Error("OpenRouter music generation response missing audio data");
return {
tracks: [{
buffer: streamResult.audioBuffer,
mimeType: outputFormatToMimeType(format),
fileName: `track-1.${format}`
}],
model,
...streamResult.transcript ? { lyrics: [streamResult.transcript] } : {},
metadata: {
inputImageCount: req.inputImages?.length ?? 0,
instrumental: req.instrumental === true,
requestedFormat: format
}
};
} finally {
await release();
}
}
};
}
const openRouterMusicTestInternals = { readOpenRouterAudioStream };
//#endregion
export { openRouterMusicTestInternals as n, buildOpenRouterMusicGenerationProvider as t };