UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

237 lines (236 loc) 8.67 kB
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 };