@juspay/neurolink 9.63.1 → 9.65.0
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/CHANGELOG.md +12 -0
- package/README.md +18 -17
- package/dist/adapters/providerImageAdapter.js +29 -1
- package/dist/adapters/replicate/auth.d.ts +19 -0
- package/dist/adapters/replicate/auth.js +32 -0
- package/dist/adapters/replicate/predictionLifecycle.d.ts +46 -0
- package/dist/adapters/replicate/predictionLifecycle.js +283 -0
- package/dist/adapters/video/klingVideoHandler.d.ts +37 -0
- package/dist/adapters/video/klingVideoHandler.js +305 -0
- package/dist/adapters/video/replicateVideoHandler.d.ts +29 -0
- package/dist/adapters/video/replicateVideoHandler.js +157 -0
- package/dist/adapters/video/runwayVideoHandler.d.ts +32 -0
- package/dist/adapters/video/runwayVideoHandler.js +316 -0
- package/dist/adapters/video/vertexVideoHandler.d.ts +19 -1
- package/dist/adapters/video/vertexVideoHandler.js +42 -11
- package/dist/autoresearch/runner.js +8 -2
- package/dist/avatar/index.d.ts +13 -0
- package/dist/avatar/index.js +13 -0
- package/dist/avatar/providers/DIDAvatar.d.ts +49 -0
- package/dist/avatar/providers/DIDAvatar.js +501 -0
- package/dist/avatar/providers/HeyGenAvatar.d.ts +30 -0
- package/dist/avatar/providers/HeyGenAvatar.js +337 -0
- package/dist/avatar/providers/ReplicateAvatar.d.ts +36 -0
- package/dist/avatar/providers/ReplicateAvatar.js +267 -0
- package/dist/browser/neurolink.min.js +573 -554
- package/dist/cli/commands/mcp.js +29 -0
- package/dist/cli/commands/proxy.js +24 -5
- package/dist/cli/factories/commandFactory.d.ts +25 -1
- package/dist/cli/factories/commandFactory.js +341 -63
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/cli/loop/optionsSchema.js +12 -0
- package/dist/constants/contextWindows.js +101 -0
- package/dist/constants/enums.d.ts +273 -2
- package/dist/constants/enums.js +290 -1
- package/dist/constants/videoErrors.d.ts +4 -0
- package/dist/constants/videoErrors.js +4 -0
- package/dist/core/baseProvider.d.ts +23 -3
- package/dist/core/baseProvider.js +217 -11
- package/dist/core/constants.d.ts +11 -0
- package/dist/core/constants.js +69 -1
- package/dist/core/modules/MessageBuilder.js +20 -0
- package/dist/core/redisConversationMemoryManager.js +6 -0
- package/dist/evaluation/index.d.ts +2 -0
- package/dist/evaluation/index.js +4 -0
- package/dist/factories/providerFactory.js +7 -1
- package/dist/factories/providerRegistry.js +203 -2
- package/dist/features/ppt/contentPlanner.js +42 -14
- package/dist/index.d.ts +9 -1
- package/dist/index.js +16 -1
- package/dist/lib/adapters/providerImageAdapter.js +29 -1
- package/dist/lib/adapters/replicate/auth.d.ts +19 -0
- package/dist/lib/adapters/replicate/auth.js +33 -0
- package/dist/lib/adapters/replicate/predictionLifecycle.d.ts +46 -0
- package/dist/lib/adapters/replicate/predictionLifecycle.js +284 -0
- package/dist/lib/adapters/video/klingVideoHandler.d.ts +37 -0
- package/dist/lib/adapters/video/klingVideoHandler.js +306 -0
- package/dist/lib/adapters/video/replicateVideoHandler.d.ts +29 -0
- package/dist/lib/adapters/video/replicateVideoHandler.js +158 -0
- package/dist/lib/adapters/video/runwayVideoHandler.d.ts +32 -0
- package/dist/lib/adapters/video/runwayVideoHandler.js +317 -0
- package/dist/lib/adapters/video/vertexVideoHandler.d.ts +19 -1
- package/dist/lib/adapters/video/vertexVideoHandler.js +42 -11
- package/dist/lib/autoresearch/runner.js +8 -2
- package/dist/lib/avatar/index.d.ts +13 -0
- package/dist/lib/avatar/index.js +14 -0
- package/dist/lib/avatar/providers/DIDAvatar.d.ts +49 -0
- package/dist/lib/avatar/providers/DIDAvatar.js +502 -0
- package/dist/lib/avatar/providers/HeyGenAvatar.d.ts +30 -0
- package/dist/lib/avatar/providers/HeyGenAvatar.js +338 -0
- package/dist/lib/avatar/providers/ReplicateAvatar.d.ts +36 -0
- package/dist/lib/avatar/providers/ReplicateAvatar.js +268 -0
- package/dist/lib/constants/contextWindows.js +101 -0
- package/dist/lib/constants/enums.d.ts +273 -2
- package/dist/lib/constants/enums.js +290 -1
- package/dist/lib/constants/videoErrors.d.ts +4 -0
- package/dist/lib/constants/videoErrors.js +4 -0
- package/dist/lib/core/baseProvider.d.ts +23 -3
- package/dist/lib/core/baseProvider.js +217 -11
- package/dist/lib/core/constants.d.ts +11 -0
- package/dist/lib/core/constants.js +69 -1
- package/dist/lib/core/modules/MessageBuilder.js +20 -0
- package/dist/lib/core/redisConversationMemoryManager.js +6 -0
- package/dist/lib/evaluation/index.d.ts +2 -0
- package/dist/lib/evaluation/index.js +4 -0
- package/dist/lib/factories/providerFactory.js +7 -1
- package/dist/lib/factories/providerRegistry.js +203 -2
- package/dist/lib/features/ppt/contentPlanner.js +42 -14
- package/dist/lib/index.d.ts +9 -1
- package/dist/lib/index.js +16 -1
- package/dist/lib/memory/hippocampusInitializer.d.ts +2 -2
- package/dist/lib/memory/hippocampusInitializer.js +32 -2
- package/dist/lib/middleware/builtin/lifecycle.js +52 -51
- package/dist/lib/music/index.d.ts +13 -0
- package/dist/lib/music/index.js +14 -0
- package/dist/lib/music/providers/BeatovenMusic.d.ts +31 -0
- package/dist/lib/music/providers/BeatovenMusic.js +334 -0
- package/dist/lib/music/providers/ElevenLabsMusic.d.ts +30 -0
- package/dist/lib/music/providers/ElevenLabsMusic.js +169 -0
- package/dist/lib/music/providers/LyriaMusic.d.ts +29 -0
- package/dist/lib/music/providers/LyriaMusic.js +173 -0
- package/dist/lib/music/providers/ReplicateMusic.d.ts +31 -0
- package/dist/lib/music/providers/ReplicateMusic.js +262 -0
- package/dist/lib/neurolink.d.ts +30 -0
- package/dist/lib/neurolink.js +342 -49
- package/dist/lib/providers/amazonBedrock.d.ts +10 -0
- package/dist/lib/providers/amazonBedrock.js +94 -39
- package/dist/lib/providers/anthropic.js +55 -7
- package/dist/lib/providers/anthropicBaseProvider.js +1 -1
- package/dist/lib/providers/azureOpenai.js +66 -17
- package/dist/lib/providers/cloudflare.d.ts +35 -0
- package/dist/lib/providers/cloudflare.js +174 -0
- package/dist/lib/providers/cohere.d.ts +52 -0
- package/dist/lib/providers/cohere.js +253 -0
- package/dist/lib/providers/deepseek.js +72 -17
- package/dist/lib/providers/fireworks.d.ts +33 -0
- package/dist/lib/providers/fireworks.js +164 -0
- package/dist/lib/providers/googleAiStudio.d.ts +11 -3
- package/dist/lib/providers/googleAiStudio.js +336 -344
- package/dist/lib/providers/googleNativeGemini3.d.ts +107 -2
- package/dist/lib/providers/googleNativeGemini3.js +381 -25
- package/dist/lib/providers/googleVertex.d.ts +116 -129
- package/dist/lib/providers/googleVertex.js +3002 -1988
- package/dist/lib/providers/groq.d.ts +33 -0
- package/dist/lib/providers/groq.js +181 -0
- package/dist/lib/providers/huggingFace.js +9 -8
- package/dist/lib/providers/ideogram.d.ts +34 -0
- package/dist/lib/providers/ideogram.js +184 -0
- package/dist/lib/providers/index.d.ts +13 -0
- package/dist/lib/providers/index.js +13 -0
- package/dist/lib/providers/jina.d.ts +59 -0
- package/dist/lib/providers/jina.js +218 -0
- package/dist/lib/providers/llamaCpp.js +14 -46
- package/dist/lib/providers/lmStudio.js +14 -47
- package/dist/lib/providers/mistral.js +7 -7
- package/dist/lib/providers/nvidiaNim.js +160 -19
- package/dist/lib/providers/ollama.js +7 -7
- package/dist/lib/providers/openAI.d.ts +22 -1
- package/dist/lib/providers/openAI.js +181 -0
- package/dist/lib/providers/openRouter.js +38 -22
- package/dist/lib/providers/openaiCompatible.js +9 -8
- package/dist/lib/providers/perplexity.d.ts +33 -0
- package/dist/lib/providers/perplexity.js +179 -0
- package/dist/lib/providers/recraft.d.ts +34 -0
- package/dist/lib/providers/recraft.js +197 -0
- package/dist/lib/providers/replicate.d.ts +75 -0
- package/dist/lib/providers/replicate.js +403 -0
- package/dist/lib/providers/stability.d.ts +37 -0
- package/dist/lib/providers/stability.js +191 -0
- package/dist/lib/providers/togetherAi.d.ts +33 -0
- package/dist/lib/providers/togetherAi.js +176 -0
- package/dist/lib/providers/voyage.d.ts +47 -0
- package/dist/lib/providers/voyage.js +177 -0
- package/dist/lib/providers/xai.d.ts +33 -0
- package/dist/lib/providers/xai.js +172 -0
- package/dist/lib/telemetry/index.d.ts +1 -1
- package/dist/lib/telemetry/index.js +1 -1
- package/dist/lib/telemetry/tracers.d.ts +19 -0
- package/dist/lib/telemetry/tracers.js +19 -0
- package/dist/lib/telemetry/withSpan.d.ts +35 -0
- package/dist/lib/telemetry/withSpan.js +103 -0
- package/dist/lib/types/aliases.d.ts +14 -0
- package/dist/lib/types/avatar.d.ts +143 -0
- package/dist/lib/types/avatar.js +20 -0
- package/dist/lib/types/cli.d.ts +6 -0
- package/dist/lib/types/common.d.ts +0 -3
- package/dist/lib/types/conversation.d.ts +10 -3
- package/dist/lib/types/generate.d.ts +76 -5
- package/dist/lib/types/index.d.ts +6 -0
- package/dist/lib/types/index.js +8 -0
- package/dist/lib/types/memory.d.ts +96 -0
- package/dist/lib/types/memory.js +23 -0
- package/dist/lib/types/middleware.d.ts +27 -0
- package/dist/lib/types/multimodal.d.ts +35 -2
- package/dist/lib/types/music.d.ts +165 -0
- package/dist/lib/types/music.js +21 -0
- package/dist/lib/types/providers.d.ts +284 -3
- package/dist/lib/types/replicate.d.ts +67 -0
- package/dist/lib/types/replicate.js +10 -0
- package/dist/lib/types/safeFetch.d.ts +15 -0
- package/dist/lib/types/safeFetch.js +7 -0
- package/dist/lib/types/stream.d.ts +8 -1
- package/dist/lib/types/tools.d.ts +13 -0
- package/dist/lib/types/video.d.ts +89 -0
- package/dist/lib/types/video.js +15 -0
- package/dist/lib/utils/avatarProcessor.d.ts +68 -0
- package/dist/lib/utils/avatarProcessor.js +172 -0
- package/dist/lib/utils/cloneOptions.d.ts +36 -0
- package/dist/lib/utils/cloneOptions.js +62 -0
- package/dist/lib/utils/lifecycleCallbacks.d.ts +56 -0
- package/dist/lib/utils/lifecycleCallbacks.js +100 -0
- package/dist/lib/utils/lifecycleTimeout.d.ts +25 -0
- package/dist/lib/utils/lifecycleTimeout.js +39 -0
- package/dist/lib/utils/logSanitize.d.ts +49 -0
- package/dist/lib/utils/logSanitize.js +170 -0
- package/dist/lib/utils/loggingFetch.d.ts +29 -0
- package/dist/lib/utils/loggingFetch.js +60 -0
- package/dist/lib/utils/messageBuilder.d.ts +10 -0
- package/dist/lib/utils/messageBuilder.js +83 -30
- package/dist/lib/utils/modelChoices.js +236 -3
- package/dist/lib/utils/modelDetection.d.ts +11 -0
- package/dist/lib/utils/modelDetection.js +27 -0
- package/dist/lib/utils/musicProcessor.d.ts +67 -0
- package/dist/lib/utils/musicProcessor.js +189 -0
- package/dist/lib/utils/optionsConversion.js +3 -2
- package/dist/lib/utils/parameterValidation.js +14 -4
- package/dist/lib/utils/pricing.js +193 -0
- package/dist/lib/utils/providerConfig.d.ts +55 -0
- package/dist/lib/utils/providerConfig.js +224 -0
- package/dist/lib/utils/providerHealth.js +7 -7
- package/dist/lib/utils/safeFetch.d.ts +26 -0
- package/dist/lib/utils/safeFetch.js +83 -0
- package/dist/lib/utils/schemaConversion.d.ts +1 -1
- package/dist/lib/utils/schemaConversion.js +59 -4
- package/dist/lib/utils/sizeGuard.d.ts +34 -0
- package/dist/lib/utils/sizeGuard.js +45 -0
- package/dist/lib/utils/ssrfGuard.d.ts +52 -0
- package/dist/lib/utils/ssrfGuard.js +411 -0
- package/dist/lib/utils/tokenLimits.js +23 -32
- package/dist/lib/utils/videoProcessor.d.ts +60 -0
- package/dist/lib/utils/videoProcessor.js +201 -0
- package/dist/lib/voice/providers/FishAudioTTS.d.ts +27 -0
- package/dist/lib/voice/providers/FishAudioTTS.js +183 -0
- package/dist/lib/workflow/core/ensembleExecutor.js +26 -9
- package/dist/memory/hippocampusInitializer.d.ts +2 -2
- package/dist/memory/hippocampusInitializer.js +32 -2
- package/dist/middleware/builtin/lifecycle.js +52 -51
- package/dist/music/index.d.ts +13 -0
- package/dist/music/index.js +13 -0
- package/dist/music/providers/BeatovenMusic.d.ts +31 -0
- package/dist/music/providers/BeatovenMusic.js +333 -0
- package/dist/music/providers/ElevenLabsMusic.d.ts +30 -0
- package/dist/music/providers/ElevenLabsMusic.js +168 -0
- package/dist/music/providers/LyriaMusic.d.ts +29 -0
- package/dist/music/providers/LyriaMusic.js +172 -0
- package/dist/music/providers/ReplicateMusic.d.ts +31 -0
- package/dist/music/providers/ReplicateMusic.js +261 -0
- package/dist/neurolink.d.ts +30 -0
- package/dist/neurolink.js +342 -49
- package/dist/providers/amazonBedrock.d.ts +10 -0
- package/dist/providers/amazonBedrock.js +94 -39
- package/dist/providers/anthropic.js +55 -7
- package/dist/providers/anthropicBaseProvider.js +1 -1
- package/dist/providers/azureOpenai.js +66 -17
- package/dist/providers/cloudflare.d.ts +35 -0
- package/dist/providers/cloudflare.js +173 -0
- package/dist/providers/cohere.d.ts +52 -0
- package/dist/providers/cohere.js +252 -0
- package/dist/providers/deepseek.js +72 -17
- package/dist/providers/fireworks.d.ts +33 -0
- package/dist/providers/fireworks.js +163 -0
- package/dist/providers/googleAiStudio.d.ts +11 -3
- package/dist/providers/googleAiStudio.js +335 -344
- package/dist/providers/googleNativeGemini3.d.ts +107 -2
- package/dist/providers/googleNativeGemini3.js +381 -25
- package/dist/providers/googleVertex.d.ts +116 -129
- package/dist/providers/googleVertex.js +3000 -1987
- package/dist/providers/groq.d.ts +33 -0
- package/dist/providers/groq.js +180 -0
- package/dist/providers/huggingFace.js +9 -8
- package/dist/providers/ideogram.d.ts +34 -0
- package/dist/providers/ideogram.js +183 -0
- package/dist/providers/index.d.ts +13 -0
- package/dist/providers/index.js +13 -0
- package/dist/providers/jina.d.ts +59 -0
- package/dist/providers/jina.js +217 -0
- package/dist/providers/llamaCpp.js +14 -46
- package/dist/providers/lmStudio.js +14 -47
- package/dist/providers/mistral.js +7 -7
- package/dist/providers/nvidiaNim.js +160 -19
- package/dist/providers/ollama.js +7 -7
- package/dist/providers/openAI.d.ts +22 -1
- package/dist/providers/openAI.js +181 -0
- package/dist/providers/openRouter.js +38 -22
- package/dist/providers/openaiCompatible.js +9 -8
- package/dist/providers/perplexity.d.ts +33 -0
- package/dist/providers/perplexity.js +178 -0
- package/dist/providers/recraft.d.ts +34 -0
- package/dist/providers/recraft.js +196 -0
- package/dist/providers/replicate.d.ts +75 -0
- package/dist/providers/replicate.js +402 -0
- package/dist/providers/stability.d.ts +37 -0
- package/dist/providers/stability.js +190 -0
- package/dist/providers/togetherAi.d.ts +33 -0
- package/dist/providers/togetherAi.js +175 -0
- package/dist/providers/voyage.d.ts +47 -0
- package/dist/providers/voyage.js +176 -0
- package/dist/providers/xai.d.ts +33 -0
- package/dist/providers/xai.js +171 -0
- package/dist/telemetry/index.d.ts +1 -1
- package/dist/telemetry/index.js +1 -1
- package/dist/telemetry/tracers.d.ts +19 -0
- package/dist/telemetry/tracers.js +19 -0
- package/dist/telemetry/withSpan.d.ts +35 -0
- package/dist/telemetry/withSpan.js +103 -0
- package/dist/types/aliases.d.ts +14 -0
- package/dist/types/avatar.d.ts +143 -0
- package/dist/types/avatar.js +19 -0
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/common.d.ts +0 -3
- package/dist/types/conversation.d.ts +10 -3
- package/dist/types/generate.d.ts +76 -5
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.js +8 -0
- package/dist/types/memory.d.ts +96 -0
- package/dist/types/memory.js +22 -0
- package/dist/types/middleware.d.ts +27 -0
- package/dist/types/multimodal.d.ts +35 -2
- package/dist/types/music.d.ts +165 -0
- package/dist/types/music.js +20 -0
- package/dist/types/providers.d.ts +284 -3
- package/dist/types/replicate.d.ts +67 -0
- package/dist/types/replicate.js +9 -0
- package/dist/types/safeFetch.d.ts +15 -0
- package/dist/types/safeFetch.js +6 -0
- package/dist/types/stream.d.ts +8 -1
- package/dist/types/tools.d.ts +13 -0
- package/dist/types/video.d.ts +89 -0
- package/dist/types/video.js +14 -0
- package/dist/utils/avatarProcessor.d.ts +68 -0
- package/dist/utils/avatarProcessor.js +171 -0
- package/dist/utils/cloneOptions.d.ts +36 -0
- package/dist/utils/cloneOptions.js +61 -0
- package/dist/utils/lifecycleCallbacks.d.ts +56 -0
- package/dist/utils/lifecycleCallbacks.js +99 -0
- package/dist/utils/lifecycleTimeout.d.ts +25 -0
- package/dist/utils/lifecycleTimeout.js +38 -0
- package/dist/utils/logSanitize.d.ts +49 -0
- package/dist/utils/logSanitize.js +169 -0
- package/dist/utils/loggingFetch.d.ts +29 -0
- package/dist/utils/loggingFetch.js +59 -0
- package/dist/utils/messageBuilder.d.ts +10 -0
- package/dist/utils/messageBuilder.js +83 -30
- package/dist/utils/modelChoices.js +236 -3
- package/dist/utils/modelDetection.d.ts +11 -0
- package/dist/utils/modelDetection.js +27 -0
- package/dist/utils/musicProcessor.d.ts +67 -0
- package/dist/utils/musicProcessor.js +188 -0
- package/dist/utils/optionsConversion.js +3 -2
- package/dist/utils/parameterValidation.js +14 -4
- package/dist/utils/pricing.js +193 -0
- package/dist/utils/providerConfig.d.ts +55 -0
- package/dist/utils/providerConfig.js +224 -0
- package/dist/utils/providerHealth.js +7 -7
- package/dist/utils/safeFetch.d.ts +26 -0
- package/dist/utils/safeFetch.js +82 -0
- package/dist/utils/schemaConversion.d.ts +1 -1
- package/dist/utils/schemaConversion.js +59 -4
- package/dist/utils/sizeGuard.d.ts +34 -0
- package/dist/utils/sizeGuard.js +44 -0
- package/dist/utils/ssrfGuard.d.ts +52 -0
- package/dist/utils/ssrfGuard.js +410 -0
- package/dist/utils/tokenLimits.js +23 -32
- package/dist/utils/videoProcessor.d.ts +60 -0
- package/dist/utils/videoProcessor.js +200 -0
- package/dist/voice/providers/FishAudioTTS.d.ts +27 -0
- package/dist/voice/providers/FishAudioTTS.js +182 -0
- package/dist/workflow/core/ensembleExecutor.js +26 -9
- package/package.json +42 -8
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ElevenLabs Music / Sound Generation Handler
|
|
3
|
+
*
|
|
4
|
+
* Distinct from ElevenLabs TTS — uses the `/v1/sound-generation` endpoint
|
|
5
|
+
* (synchronous; returns binary audio directly, no polling).
|
|
6
|
+
*
|
|
7
|
+
* @module music/providers/ElevenLabsMusic
|
|
8
|
+
* @see https://elevenlabs.io/docs/api-reference/sound-generation
|
|
9
|
+
*/
|
|
10
|
+
import { ErrorCategory, ErrorSeverity } from "../../constants/enums.js";
|
|
11
|
+
import { MUSIC_ERROR_CODES, MusicError } from "../../utils/musicProcessor.js";
|
|
12
|
+
import { logger } from "../../utils/logger.js";
|
|
13
|
+
import { MAX_AUDIO_BYTES, readBoundedBuffer } from "../../utils/sizeGuard.js";
|
|
14
|
+
const DEFAULT_BASE_URL = "https://api.elevenlabs.io/v1";
|
|
15
|
+
const REQUEST_TIMEOUT_MS = 60_000; // longer because synchronous generation
|
|
16
|
+
/**
|
|
17
|
+
* ElevenLabs Music / Sound Generation Handler.
|
|
18
|
+
*
|
|
19
|
+
* Auth: `xi-api-key: ${ELEVENLABS_API_KEY}` (shares the same env var as
|
|
20
|
+
* ElevenLabs TTS — same account; different endpoint).
|
|
21
|
+
*
|
|
22
|
+
* Best for: short sound effects (ambient drones, hits, foley) and short
|
|
23
|
+
* music loops up to 22 seconds.
|
|
24
|
+
*/
|
|
25
|
+
export class ElevenLabsMusic {
|
|
26
|
+
maxDurationSeconds = 22;
|
|
27
|
+
supportedFormats = ["mp3"];
|
|
28
|
+
supportedGenres = [
|
|
29
|
+
"ambient",
|
|
30
|
+
"cinematic",
|
|
31
|
+
"lo-fi",
|
|
32
|
+
"electronic",
|
|
33
|
+
"orchestral",
|
|
34
|
+
"soundscape",
|
|
35
|
+
];
|
|
36
|
+
apiKey;
|
|
37
|
+
baseUrl;
|
|
38
|
+
constructor(apiKey) {
|
|
39
|
+
const resolved = (apiKey ?? process.env.ELEVENLABS_API_KEY ?? "").trim();
|
|
40
|
+
this.apiKey = resolved.length > 0 ? resolved : null;
|
|
41
|
+
this.baseUrl = (process.env.ELEVENLABS_BASE_URL ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
42
|
+
}
|
|
43
|
+
isConfigured() {
|
|
44
|
+
return this.apiKey !== null;
|
|
45
|
+
}
|
|
46
|
+
async generate(options) {
|
|
47
|
+
if (!this.apiKey) {
|
|
48
|
+
throw new MusicError({
|
|
49
|
+
code: MUSIC_ERROR_CODES.PROVIDER_NOT_CONFIGURED,
|
|
50
|
+
message: "ELEVENLABS_API_KEY not configured (shared with ElevenLabs TTS — same account, different endpoint)",
|
|
51
|
+
category: ErrorCategory.CONFIGURATION,
|
|
52
|
+
severity: ErrorSeverity.HIGH,
|
|
53
|
+
retriable: false,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const promptText = options.prompt?.trim();
|
|
57
|
+
if (!promptText) {
|
|
58
|
+
throw new MusicError({
|
|
59
|
+
code: MUSIC_ERROR_CODES.INVALID_INPUT,
|
|
60
|
+
message: "ElevenLabs Music requires a non-empty prompt",
|
|
61
|
+
category: ErrorCategory.VALIDATION,
|
|
62
|
+
severity: ErrorSeverity.LOW,
|
|
63
|
+
retriable: false,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const requestedDuration = options.duration ?? 8;
|
|
67
|
+
if (requestedDuration <= 0 || requestedDuration > this.maxDurationSeconds) {
|
|
68
|
+
throw new MusicError({
|
|
69
|
+
code: MUSIC_ERROR_CODES.INVALID_INPUT,
|
|
70
|
+
message: `ElevenLabs Music duration must be between 1 and ${this.maxDurationSeconds} seconds; got ${requestedDuration}`,
|
|
71
|
+
category: ErrorCategory.VALIDATION,
|
|
72
|
+
severity: ErrorSeverity.LOW,
|
|
73
|
+
retriable: false,
|
|
74
|
+
context: {
|
|
75
|
+
requested: requestedDuration,
|
|
76
|
+
maximum: this.maxDurationSeconds,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const startTime = Date.now();
|
|
81
|
+
const duration = requestedDuration;
|
|
82
|
+
const body = {
|
|
83
|
+
text: this.buildPrompt({ ...options, prompt: promptText }),
|
|
84
|
+
duration_seconds: duration,
|
|
85
|
+
prompt_influence: 0.3,
|
|
86
|
+
};
|
|
87
|
+
const controller = new AbortController();
|
|
88
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
89
|
+
let response;
|
|
90
|
+
try {
|
|
91
|
+
response = await fetch(`${this.baseUrl}/sound-generation`, {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: {
|
|
94
|
+
"xi-api-key": this.apiKey,
|
|
95
|
+
"Content-Type": "application/json",
|
|
96
|
+
Accept: "audio/mpeg",
|
|
97
|
+
},
|
|
98
|
+
body: JSON.stringify(body),
|
|
99
|
+
signal: controller.signal,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
104
|
+
throw new MusicError({
|
|
105
|
+
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
|
|
106
|
+
message: `ElevenLabs Music request timed out after ${REQUEST_TIMEOUT_MS / 1000}s`,
|
|
107
|
+
category: ErrorCategory.NETWORK,
|
|
108
|
+
severity: ErrorSeverity.HIGH,
|
|
109
|
+
retriable: true,
|
|
110
|
+
originalError: err,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
throw new MusicError({
|
|
114
|
+
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
|
|
115
|
+
message: `ElevenLabs Music network error: ${err instanceof Error ? err.message : String(err)}`,
|
|
116
|
+
category: ErrorCategory.NETWORK,
|
|
117
|
+
severity: ErrorSeverity.HIGH,
|
|
118
|
+
retriable: true,
|
|
119
|
+
originalError: err instanceof Error ? err : undefined,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
clearTimeout(timeoutId);
|
|
124
|
+
}
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
const text = await response.text();
|
|
127
|
+
const retriable = response.status === 408 ||
|
|
128
|
+
response.status === 429 ||
|
|
129
|
+
response.status >= 500;
|
|
130
|
+
throw new MusicError({
|
|
131
|
+
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
|
|
132
|
+
message: `ElevenLabs Music synthesis failed: ${response.status} — ${text}`,
|
|
133
|
+
category: retriable ? ErrorCategory.NETWORK : ErrorCategory.EXECUTION,
|
|
134
|
+
severity: ErrorSeverity.HIGH,
|
|
135
|
+
retriable,
|
|
136
|
+
context: { status: response.status },
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
const buffer = await readBoundedBuffer(response, MAX_AUDIO_BYTES, "ElevenLabs music");
|
|
140
|
+
const latency = Date.now() - startTime;
|
|
141
|
+
logger.info(`[ElevenLabsMusic] Generated ${buffer.length} bytes in ${latency}ms`);
|
|
142
|
+
return {
|
|
143
|
+
buffer,
|
|
144
|
+
format: "mp3",
|
|
145
|
+
size: buffer.length,
|
|
146
|
+
duration,
|
|
147
|
+
provider: "elevenlabs-music",
|
|
148
|
+
metadata: {
|
|
149
|
+
latency,
|
|
150
|
+
provider: "elevenlabs-music",
|
|
151
|
+
model: "elevenlabs-sound-v1",
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
buildPrompt(options) {
|
|
156
|
+
const parts = [options.prompt];
|
|
157
|
+
if (options.genre) {
|
|
158
|
+
parts.push(`Genre: ${options.genre}`);
|
|
159
|
+
}
|
|
160
|
+
if (options.mood) {
|
|
161
|
+
parts.push(`Mood: ${options.mood}`);
|
|
162
|
+
}
|
|
163
|
+
if (options.tempo !== undefined) {
|
|
164
|
+
parts.push(`${options.tempo} BPM`);
|
|
165
|
+
}
|
|
166
|
+
return parts.join(". ");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Lyria 3 Pro Music Handler
|
|
3
|
+
*
|
|
4
|
+
* Synchronous generation against the Generative Language API. Returns
|
|
5
|
+
* audio inline as base64 in the response.
|
|
6
|
+
*
|
|
7
|
+
* @module music/providers/LyriaMusic
|
|
8
|
+
* @see https://ai.google.dev/gemini-api/docs/music-generation
|
|
9
|
+
*/
|
|
10
|
+
import type { MusicAudioFormat, MusicHandler, MusicOptions, MusicResult } from "../../types/index.js";
|
|
11
|
+
/**
|
|
12
|
+
* Google Lyria 3 Pro Music Handler.
|
|
13
|
+
*
|
|
14
|
+
* Auth: `Authorization: Bearer ${GOOGLE_API_KEY}` or query-string
|
|
15
|
+
* `?key=${GOOGLE_API_KEY}` (the latter is more compatible with the
|
|
16
|
+
* Generative Language endpoints today).
|
|
17
|
+
*/
|
|
18
|
+
export declare class LyriaMusic implements MusicHandler {
|
|
19
|
+
readonly maxDurationSeconds = 30;
|
|
20
|
+
readonly supportedFormats: readonly MusicAudioFormat[];
|
|
21
|
+
readonly supportedGenres: readonly string[];
|
|
22
|
+
private readonly apiKey;
|
|
23
|
+
private readonly baseUrl;
|
|
24
|
+
private readonly model;
|
|
25
|
+
constructor(apiKey?: string);
|
|
26
|
+
isConfigured(): boolean;
|
|
27
|
+
generate(options: MusicOptions): Promise<MusicResult>;
|
|
28
|
+
private buildPrompt;
|
|
29
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Lyria 3 Pro Music Handler
|
|
3
|
+
*
|
|
4
|
+
* Synchronous generation against the Generative Language API. Returns
|
|
5
|
+
* audio inline as base64 in the response.
|
|
6
|
+
*
|
|
7
|
+
* @module music/providers/LyriaMusic
|
|
8
|
+
* @see https://ai.google.dev/gemini-api/docs/music-generation
|
|
9
|
+
*/
|
|
10
|
+
import { ErrorCategory, ErrorSeverity } from "../../constants/enums.js";
|
|
11
|
+
import { MUSIC_ERROR_CODES, MusicError } from "../../utils/musicProcessor.js";
|
|
12
|
+
import { logger } from "../../utils/logger.js";
|
|
13
|
+
import { sanitizeForLog } from "../../utils/logSanitize.js";
|
|
14
|
+
const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
15
|
+
const DEFAULT_MODEL = "lyria-3-pro-preview";
|
|
16
|
+
const REQUEST_TIMEOUT_MS = 120_000;
|
|
17
|
+
/**
|
|
18
|
+
* Google Lyria 3 Pro Music Handler.
|
|
19
|
+
*
|
|
20
|
+
* Auth: `Authorization: Bearer ${GOOGLE_API_KEY}` or query-string
|
|
21
|
+
* `?key=${GOOGLE_API_KEY}` (the latter is more compatible with the
|
|
22
|
+
* Generative Language endpoints today).
|
|
23
|
+
*/
|
|
24
|
+
export class LyriaMusic {
|
|
25
|
+
maxDurationSeconds = 30;
|
|
26
|
+
supportedFormats = ["wav"];
|
|
27
|
+
supportedGenres = [
|
|
28
|
+
"ambient",
|
|
29
|
+
"classical",
|
|
30
|
+
"electronic",
|
|
31
|
+
"jazz",
|
|
32
|
+
"rock",
|
|
33
|
+
"pop",
|
|
34
|
+
"lo-fi",
|
|
35
|
+
"cinematic",
|
|
36
|
+
"orchestral",
|
|
37
|
+
"world",
|
|
38
|
+
];
|
|
39
|
+
apiKey;
|
|
40
|
+
baseUrl;
|
|
41
|
+
model;
|
|
42
|
+
constructor(apiKey) {
|
|
43
|
+
const resolved = (apiKey ??
|
|
44
|
+
process.env.GOOGLE_AI_LYRIA_API_KEY ??
|
|
45
|
+
process.env.GOOGLE_API_KEY ??
|
|
46
|
+
process.env.GOOGLE_AI_API_KEY ??
|
|
47
|
+
process.env.GEMINI_API_KEY ??
|
|
48
|
+
"").trim();
|
|
49
|
+
this.apiKey = resolved.length > 0 ? resolved : null;
|
|
50
|
+
this.baseUrl = (process.env.LYRIA_BASE_URL ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
51
|
+
this.model = process.env.LYRIA_MODEL ?? DEFAULT_MODEL;
|
|
52
|
+
}
|
|
53
|
+
isConfigured() {
|
|
54
|
+
return this.apiKey !== null;
|
|
55
|
+
}
|
|
56
|
+
async generate(options) {
|
|
57
|
+
if (!this.apiKey) {
|
|
58
|
+
throw new MusicError({
|
|
59
|
+
code: MUSIC_ERROR_CODES.PROVIDER_NOT_CONFIGURED,
|
|
60
|
+
message: "Lyria requires one of: GOOGLE_API_KEY, GOOGLE_AI_LYRIA_API_KEY, GOOGLE_AI_API_KEY, or GEMINI_API_KEY",
|
|
61
|
+
category: ErrorCategory.CONFIGURATION,
|
|
62
|
+
severity: ErrorSeverity.HIGH,
|
|
63
|
+
retriable: false,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const startTime = Date.now();
|
|
67
|
+
const duration = Math.min(options.duration ?? 16, this.maxDurationSeconds);
|
|
68
|
+
// Lyria API no longer accepts `audioGenerationOptions` — duration is
|
|
69
|
+
// controlled implicitly by the prompt/model. Only `responseModalities`
|
|
70
|
+
// is allowed under `generationConfig`. Embed duration in the prompt
|
|
71
|
+
// so the model still gets the hint.
|
|
72
|
+
const promptWithDuration = `${this.buildPrompt(options)}. Duration: ${duration} seconds`;
|
|
73
|
+
const body = {
|
|
74
|
+
contents: [
|
|
75
|
+
{
|
|
76
|
+
parts: [
|
|
77
|
+
{
|
|
78
|
+
text: promptWithDuration,
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
generationConfig: {
|
|
84
|
+
responseModalities: ["AUDIO"],
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
const controller = new AbortController();
|
|
88
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
89
|
+
let response;
|
|
90
|
+
try {
|
|
91
|
+
response = await fetch(`${this.baseUrl}/models/${this.model}:generateContent?key=${this.apiKey}`, {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: {
|
|
94
|
+
"Content-Type": "application/json",
|
|
95
|
+
},
|
|
96
|
+
body: JSON.stringify(body),
|
|
97
|
+
signal: controller.signal,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
102
|
+
throw new MusicError({
|
|
103
|
+
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
|
|
104
|
+
message: `Lyria request timed out after ${REQUEST_TIMEOUT_MS / 1000}s`,
|
|
105
|
+
category: ErrorCategory.NETWORK,
|
|
106
|
+
severity: ErrorSeverity.HIGH,
|
|
107
|
+
retriable: true,
|
|
108
|
+
originalError: err,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
clearTimeout(timeoutId);
|
|
115
|
+
}
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
const rawText = await response.text();
|
|
118
|
+
const retriable = response.status === 408 ||
|
|
119
|
+
response.status === 429 ||
|
|
120
|
+
response.status >= 500;
|
|
121
|
+
throw new MusicError({
|
|
122
|
+
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
|
|
123
|
+
message: `Lyria generation failed: ${response.status} — ${sanitizeForLog(rawText)}`,
|
|
124
|
+
category: retriable ? ErrorCategory.NETWORK : ErrorCategory.EXECUTION,
|
|
125
|
+
severity: ErrorSeverity.HIGH,
|
|
126
|
+
retriable,
|
|
127
|
+
context: { status: response.status },
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const data = (await response.json());
|
|
131
|
+
const audioPart = data.candidates?.[0]?.content?.parts?.find((p) => p.inlineData?.mimeType?.startsWith("audio/"));
|
|
132
|
+
if (!audioPart?.inlineData?.data) {
|
|
133
|
+
throw new MusicError({
|
|
134
|
+
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
|
|
135
|
+
message: "Lyria response missing audio data",
|
|
136
|
+
category: ErrorCategory.EXECUTION,
|
|
137
|
+
severity: ErrorSeverity.HIGH,
|
|
138
|
+
retriable: false,
|
|
139
|
+
context: { response: data },
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const buffer = Buffer.from(audioPart.inlineData.data, "base64");
|
|
143
|
+
const latency = Date.now() - startTime;
|
|
144
|
+
logger.info(`[LyriaMusic] Generated ${buffer.length} bytes in ${latency}ms — model ${this.model}`);
|
|
145
|
+
return {
|
|
146
|
+
buffer,
|
|
147
|
+
format: "wav",
|
|
148
|
+
size: buffer.length,
|
|
149
|
+
duration,
|
|
150
|
+
provider: "lyria",
|
|
151
|
+
metadata: {
|
|
152
|
+
latency,
|
|
153
|
+
provider: "lyria",
|
|
154
|
+
model: this.model,
|
|
155
|
+
sampleRate: 48_000,
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
buildPrompt(options) {
|
|
160
|
+
const parts = [options.prompt];
|
|
161
|
+
if (options.genre) {
|
|
162
|
+
parts.push(`Genre: ${options.genre}`);
|
|
163
|
+
}
|
|
164
|
+
if (options.mood) {
|
|
165
|
+
parts.push(`Mood: ${options.mood}`);
|
|
166
|
+
}
|
|
167
|
+
if (options.tempo !== undefined) {
|
|
168
|
+
parts.push(`${options.tempo} BPM`);
|
|
169
|
+
}
|
|
170
|
+
return parts.join(". ");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replicate Music Handler (MusicGen default)
|
|
3
|
+
*
|
|
4
|
+
* Routes music generation through Replicate's universal prediction
|
|
5
|
+
* lifecycle. Default model is Meta's MusicGen; alternatives include
|
|
6
|
+
* Riffusion, AudioGen, and AudioLDM via `options.model`.
|
|
7
|
+
*
|
|
8
|
+
* @module music/providers/ReplicateMusic
|
|
9
|
+
* @see https://replicate.com/meta/musicgen
|
|
10
|
+
*/
|
|
11
|
+
import type { MusicAudioFormat, MusicHandler, MusicOptions, MusicResult } from "../../types/index.js";
|
|
12
|
+
export declare class ReplicateMusic implements MusicHandler {
|
|
13
|
+
readonly maxDurationSeconds = 30;
|
|
14
|
+
readonly supportedFormats: readonly MusicAudioFormat[];
|
|
15
|
+
readonly supportedGenres: readonly string[];
|
|
16
|
+
isConfigured(): boolean;
|
|
17
|
+
generate(options: MusicOptions): Promise<MusicResult>;
|
|
18
|
+
private buildPrompt;
|
|
19
|
+
private resolveBuffer;
|
|
20
|
+
/**
|
|
21
|
+
* Detect audio MIME subtype from magic bytes.
|
|
22
|
+
*
|
|
23
|
+
* - WAV : "RIFF" header (52 49 46 46)
|
|
24
|
+
* - MP3 : ID3 tag (49 44 33) or MPEG sync word 0xFF 0xEx
|
|
25
|
+
* - OGG : "OggS" capture pattern (4F 67 67 53)
|
|
26
|
+
* - M4A : "ftyp" box at offset 4
|
|
27
|
+
*
|
|
28
|
+
* Falls back to "mp3" when detection is inconclusive.
|
|
29
|
+
*/
|
|
30
|
+
private detectAudioType;
|
|
31
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replicate Music Handler (MusicGen default)
|
|
3
|
+
*
|
|
4
|
+
* Routes music generation through Replicate's universal prediction
|
|
5
|
+
* lifecycle. Default model is Meta's MusicGen; alternatives include
|
|
6
|
+
* Riffusion, AudioGen, and AudioLDM via `options.model`.
|
|
7
|
+
*
|
|
8
|
+
* @module music/providers/ReplicateMusic
|
|
9
|
+
* @see https://replicate.com/meta/musicgen
|
|
10
|
+
*/
|
|
11
|
+
import { ErrorCategory, ErrorSeverity } from "../../constants/enums.js";
|
|
12
|
+
import { MUSIC_ERROR_CODES, MusicError } from "../../utils/musicProcessor.js";
|
|
13
|
+
import { logger } from "../../utils/logger.js";
|
|
14
|
+
import { getReplicateAuth } from "../../adapters/replicate/auth.js";
|
|
15
|
+
import { downloadPredictionOutput, predict, } from "../../adapters/replicate/predictionLifecycle.js";
|
|
16
|
+
import { MAX_AUDIO_BYTES, readBoundedBuffer } from "../../utils/sizeGuard.js";
|
|
17
|
+
import { assertSafeUrl } from "../../utils/ssrfGuard.js";
|
|
18
|
+
const DEFAULT_MODEL = "meta/musicgen:7be0f12c54a8d033a0fbd14418c9af98962da9a86f5ff7811f9b3423a1f0b7d7";
|
|
19
|
+
export class ReplicateMusic {
|
|
20
|
+
maxDurationSeconds = 30;
|
|
21
|
+
supportedFormats = [
|
|
22
|
+
"mp3",
|
|
23
|
+
"wav",
|
|
24
|
+
];
|
|
25
|
+
supportedGenres = [
|
|
26
|
+
"ambient",
|
|
27
|
+
"classical",
|
|
28
|
+
"electronic",
|
|
29
|
+
"rock",
|
|
30
|
+
"jazz",
|
|
31
|
+
"hip-hop",
|
|
32
|
+
"pop",
|
|
33
|
+
"lo-fi",
|
|
34
|
+
"cinematic",
|
|
35
|
+
"orchestral",
|
|
36
|
+
];
|
|
37
|
+
isConfigured() {
|
|
38
|
+
return getReplicateAuth() !== null;
|
|
39
|
+
}
|
|
40
|
+
async generate(options) {
|
|
41
|
+
const auth = getReplicateAuth();
|
|
42
|
+
if (!auth) {
|
|
43
|
+
throw new MusicError({
|
|
44
|
+
code: MUSIC_ERROR_CODES.PROVIDER_NOT_CONFIGURED,
|
|
45
|
+
message: "REPLICATE_API_TOKEN not configured",
|
|
46
|
+
category: ErrorCategory.CONFIGURATION,
|
|
47
|
+
severity: ErrorSeverity.HIGH,
|
|
48
|
+
retriable: false,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const startTime = Date.now();
|
|
52
|
+
const model = options.model ?? DEFAULT_MODEL;
|
|
53
|
+
const requestedFormat = options.format ?? "mp3";
|
|
54
|
+
const upstreamFormat = this.supportedFormats.includes(requestedFormat)
|
|
55
|
+
? requestedFormat
|
|
56
|
+
: "mp3";
|
|
57
|
+
// Clamp to provider max and apply default; store as `effectiveDuration`
|
|
58
|
+
// so the returned result always reflects the audio that was actually generated.
|
|
59
|
+
const effectiveDuration = Math.min(options.duration ?? 8, this.maxDurationSeconds);
|
|
60
|
+
// MusicGen accepts these inputs; other models override via cast.
|
|
61
|
+
const inputPayload = {
|
|
62
|
+
prompt: this.buildPrompt(options),
|
|
63
|
+
duration: effectiveDuration,
|
|
64
|
+
output_format: upstreamFormat,
|
|
65
|
+
model_version: "stereo-large",
|
|
66
|
+
normalization_strategy: "loudness",
|
|
67
|
+
};
|
|
68
|
+
if (options.tempo !== undefined) {
|
|
69
|
+
inputPayload.bpm = options.tempo;
|
|
70
|
+
}
|
|
71
|
+
if (options.referenceAudio) {
|
|
72
|
+
const ref = await this.resolveBuffer(options.referenceAudio);
|
|
73
|
+
inputPayload.input_audio = `data:audio/${this.detectAudioType(ref)};base64,${ref.toString("base64")}`;
|
|
74
|
+
}
|
|
75
|
+
let prediction;
|
|
76
|
+
try {
|
|
77
|
+
prediction = await predict(auth, { model, input: inputPayload });
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
throw new MusicError({
|
|
81
|
+
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
|
|
82
|
+
message: `Replicate music generation failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
83
|
+
category: ErrorCategory.EXECUTION,
|
|
84
|
+
severity: ErrorSeverity.HIGH,
|
|
85
|
+
retriable: true,
|
|
86
|
+
// Sanitize context: omit raw `options` which may contain large Buffers
|
|
87
|
+
// (referenceAudio) and arbitrary user content.
|
|
88
|
+
context: {
|
|
89
|
+
model,
|
|
90
|
+
duration: effectiveDuration,
|
|
91
|
+
format: upstreamFormat,
|
|
92
|
+
hasReferenceAudio: options.referenceAudio !== undefined,
|
|
93
|
+
},
|
|
94
|
+
originalError: err instanceof Error ? err : undefined,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
let buffer;
|
|
98
|
+
try {
|
|
99
|
+
buffer = await downloadPredictionOutput(prediction, MAX_AUDIO_BYTES);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
throw new MusicError({
|
|
103
|
+
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
|
|
104
|
+
message: `Replicate music download failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
105
|
+
category: ErrorCategory.NETWORK,
|
|
106
|
+
severity: ErrorSeverity.MEDIUM,
|
|
107
|
+
retriable: true,
|
|
108
|
+
context: { predictionId: prediction.id },
|
|
109
|
+
originalError: err instanceof Error ? err : undefined,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const latency = Date.now() - startTime;
|
|
113
|
+
logger.info(`[ReplicateMusic] Generated ${buffer.length} bytes in ${latency}ms — model ${model}`);
|
|
114
|
+
return {
|
|
115
|
+
buffer,
|
|
116
|
+
format: upstreamFormat,
|
|
117
|
+
size: buffer.length,
|
|
118
|
+
// Return the clamped/defaulted duration actually sent to the model,
|
|
119
|
+
// not the raw options.duration which may have been out of bounds or undefined.
|
|
120
|
+
duration: effectiveDuration,
|
|
121
|
+
provider: "replicate",
|
|
122
|
+
metadata: {
|
|
123
|
+
latency,
|
|
124
|
+
provider: "replicate",
|
|
125
|
+
model,
|
|
126
|
+
jobId: prediction.id,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
buildPrompt(options) {
|
|
131
|
+
const parts = [options.prompt];
|
|
132
|
+
if (options.genre) {
|
|
133
|
+
parts.push(`Genre: ${options.genre}`);
|
|
134
|
+
}
|
|
135
|
+
if (options.mood) {
|
|
136
|
+
parts.push(`Mood: ${options.mood}`);
|
|
137
|
+
}
|
|
138
|
+
if (options.tempo !== undefined) {
|
|
139
|
+
parts.push(`${options.tempo} BPM`);
|
|
140
|
+
}
|
|
141
|
+
return parts.join(". ");
|
|
142
|
+
}
|
|
143
|
+
async resolveBuffer(input) {
|
|
144
|
+
if (Buffer.isBuffer(input)) {
|
|
145
|
+
return input;
|
|
146
|
+
}
|
|
147
|
+
// Reject local file paths — only Buffer or HTTPS URLs are accepted.
|
|
148
|
+
if (!/^https:\/\//.test(input)) {
|
|
149
|
+
throw new MusicError({
|
|
150
|
+
code: MUSIC_ERROR_CODES.INVALID_INPUT,
|
|
151
|
+
message: `Invalid input: expected Buffer or HTTPS URL, got string "${input}". Local file reads are not supported.`,
|
|
152
|
+
category: ErrorCategory.VALIDATION,
|
|
153
|
+
severity: ErrorSeverity.HIGH,
|
|
154
|
+
retriable: false,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
await assertSafeUrl(input);
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
throw new MusicError({
|
|
162
|
+
code: MUSIC_ERROR_CODES.INVALID_INPUT,
|
|
163
|
+
message: `Unsafe URL rejected: ${err instanceof Error ? err.message : String(err)}`,
|
|
164
|
+
category: ErrorCategory.VALIDATION,
|
|
165
|
+
severity: ErrorSeverity.HIGH,
|
|
166
|
+
retriable: false,
|
|
167
|
+
context: { url: input },
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
const FETCH_TIMEOUT_MS = 60_000;
|
|
171
|
+
const controller = new AbortController();
|
|
172
|
+
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
173
|
+
let r;
|
|
174
|
+
try {
|
|
175
|
+
r = await fetch(input, { signal: controller.signal });
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
179
|
+
throw new MusicError({
|
|
180
|
+
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
|
|
181
|
+
message: `Replicate music reference-audio fetch timed out after ${FETCH_TIMEOUT_MS / 1000}s: ${input}`,
|
|
182
|
+
category: ErrorCategory.NETWORK,
|
|
183
|
+
severity: ErrorSeverity.MEDIUM,
|
|
184
|
+
retriable: true,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
clearTimeout(timeoutId);
|
|
191
|
+
}
|
|
192
|
+
if (!r.ok) {
|
|
193
|
+
throw new MusicError({
|
|
194
|
+
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
|
|
195
|
+
message: `Failed to fetch reference audio: ${input}: ${r.status}`,
|
|
196
|
+
category: ErrorCategory.NETWORK,
|
|
197
|
+
severity: ErrorSeverity.MEDIUM,
|
|
198
|
+
retriable: r.status >= 500,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
return await readBoundedBuffer(r, MAX_AUDIO_BYTES, "Replicate reference audio");
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
throw new MusicError({
|
|
206
|
+
code: MUSIC_ERROR_CODES.INVALID_INPUT,
|
|
207
|
+
message: `Replicate reference audio too large: ${err instanceof Error ? err.message : String(err)}`,
|
|
208
|
+
category: ErrorCategory.VALIDATION,
|
|
209
|
+
severity: ErrorSeverity.HIGH,
|
|
210
|
+
retriable: false,
|
|
211
|
+
context: { url: input },
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Detect audio MIME subtype from magic bytes.
|
|
217
|
+
*
|
|
218
|
+
* - WAV : "RIFF" header (52 49 46 46)
|
|
219
|
+
* - MP3 : ID3 tag (49 44 33) or MPEG sync word 0xFF 0xEx
|
|
220
|
+
* - OGG : "OggS" capture pattern (4F 67 67 53)
|
|
221
|
+
* - M4A : "ftyp" box at offset 4
|
|
222
|
+
*
|
|
223
|
+
* Falls back to "mp3" when detection is inconclusive.
|
|
224
|
+
*/
|
|
225
|
+
detectAudioType(buffer) {
|
|
226
|
+
if (buffer.length < 4) {
|
|
227
|
+
return "mp3";
|
|
228
|
+
}
|
|
229
|
+
// WAV: starts with RIFF
|
|
230
|
+
if (buffer[0] === 0x52 &&
|
|
231
|
+
buffer[1] === 0x49 &&
|
|
232
|
+
buffer[2] === 0x46 &&
|
|
233
|
+
buffer[3] === 0x46) {
|
|
234
|
+
return "wav";
|
|
235
|
+
}
|
|
236
|
+
// OGG: starts with OggS
|
|
237
|
+
if (buffer[0] === 0x4f &&
|
|
238
|
+
buffer[1] === 0x67 &&
|
|
239
|
+
buffer[2] === 0x67 &&
|
|
240
|
+
buffer[3] === 0x53) {
|
|
241
|
+
return "ogg";
|
|
242
|
+
}
|
|
243
|
+
// MP3: ID3 header
|
|
244
|
+
if (buffer[0] === 0x49 && buffer[1] === 0x44 && buffer[2] === 0x33) {
|
|
245
|
+
return "mp3";
|
|
246
|
+
}
|
|
247
|
+
// MP3: MPEG sync word (0xFF 0xE0–0xFF)
|
|
248
|
+
if (buffer[0] === 0xff && (buffer[1] & 0xe0) === 0xe0) {
|
|
249
|
+
return "mpeg";
|
|
250
|
+
}
|
|
251
|
+
// M4A / AAC: "ftyp" box at offset 4
|
|
252
|
+
if (buffer.length >= 8 &&
|
|
253
|
+
buffer[4] === 0x66 &&
|
|
254
|
+
buffer[5] === 0x74 &&
|
|
255
|
+
buffer[6] === 0x79 &&
|
|
256
|
+
buffer[7] === 0x70) {
|
|
257
|
+
return "mp4";
|
|
258
|
+
}
|
|
259
|
+
return "mp3";
|
|
260
|
+
}
|
|
261
|
+
}
|