@elizaos/plugin-suno 1.0.6-alpha.3 → 2.0.0-beta.1

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/dist/index.js CHANGED
@@ -1,10 +1,13 @@
1
1
  // src/providers/suno.ts
2
+ import {
3
+ recordLlmCall
4
+ } from "@elizaos/core";
2
5
  var SunoProvider = class _SunoProvider {
3
6
  apiKey;
4
7
  baseUrl;
5
8
  static async get(runtime, _message, _state) {
6
9
  const apiKey = runtime.getSetting("SUNO_API_KEY");
7
- if (!apiKey) {
10
+ if (typeof apiKey !== "string" || !apiKey) {
8
11
  throw new Error("SUNO_API_KEY is required");
9
12
  }
10
13
  return new _SunoProvider({ apiKey });
@@ -16,425 +19,293 @@ var SunoProvider = class _SunoProvider {
16
19
  async get(_runtime, _message, _state) {
17
20
  return { status: "ready" };
18
21
  }
19
- async request(endpoint, options = {}) {
22
+ async request(runtime, endpoint, options = {}) {
20
23
  const url = `${this.baseUrl}${endpoint}`;
21
24
  const headers = {
22
- "Authorization": `Bearer ${this.apiKey}`,
25
+ Authorization: `Bearer ${this.apiKey}`,
23
26
  "Content-Type": "application/json",
24
27
  ...options.headers
25
28
  };
26
- const response = await fetch(url, {
27
- ...options,
28
- headers
29
+ const body = typeof options.body === "string" ? options.body : "";
30
+ const details = {
31
+ model: "suno",
32
+ modelVersion: "api-v1",
33
+ systemPrompt: "Suno music generation API request",
34
+ userPrompt: body,
35
+ temperature: readTemperature(body),
36
+ maxTokens: 0,
37
+ purpose: "action",
38
+ actionType: `suno.fetch${endpoint}`
39
+ };
40
+ return recordLlmCall(runtime, details, async () => {
41
+ const response = await fetch(url, {
42
+ ...options,
43
+ headers
44
+ });
45
+ if (!response.ok) {
46
+ throw new Error(`Suno API error: ${response.statusText}`);
47
+ }
48
+ const data = await response.json();
49
+ details.response = JSON.stringify({ suno_response: data });
50
+ return data;
29
51
  });
30
- if (!response.ok) {
31
- throw new Error(`Suno API error: ${response.statusText}`);
32
- }
33
- return response.json();
52
+ }
53
+ };
54
+ function readTemperature(body) {
55
+ if (!body) return 0;
56
+ try {
57
+ const parsed = JSON.parse(body);
58
+ return typeof parsed.temperature === "number" ? parsed.temperature : 0;
59
+ } catch {
60
+ return 0;
61
+ }
62
+ }
63
+ var sunoStatusProvider = {
64
+ name: "SUNO_STATUS",
65
+ description: "Suno music generation status",
66
+ descriptionCompressed: "Suno generation availability.",
67
+ contexts: ["media"],
68
+ contextGate: { anyOf: ["media"] },
69
+ cacheStable: false,
70
+ cacheScope: "turn",
71
+ get: async (runtime) => {
72
+ const configured = Boolean(runtime.getSetting("SUNO_API_KEY"));
73
+ return {
74
+ text: JSON.stringify(
75
+ {
76
+ suno: {
77
+ configured,
78
+ status: configured ? "ready" : "missing_api_key",
79
+ action: "MUSIC_GENERATION",
80
+ subactions: ["generate", "custom", "extend"]
81
+ }
82
+ },
83
+ null,
84
+ 2
85
+ ),
86
+ data: { configured },
87
+ values: { sunoConfigured: configured }
88
+ };
34
89
  }
35
90
  };
36
91
 
37
- // src/actions/generate.ts
38
- var generateMusic = {
39
- name: "generate-music",
40
- description: "Generate music using Suno AI",
92
+ // src/actions/musicGeneration.ts
93
+ var SUNO_ACTION_TIMEOUT_MS = 3e4;
94
+ var MAX_SUNO_RESPONSE_BYTES = 4e3;
95
+ function paramsFromMessageAndOptions(message, options) {
96
+ const content = message.content && typeof message.content === "object" ? message.content : {};
97
+ const parameters = (options == null ? void 0 : options.parameters) && typeof options.parameters === "object" ? options.parameters : {};
98
+ return { ...content, ...options, ...parameters };
99
+ }
100
+ function normalizeSubaction(value) {
101
+ const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
102
+ if (normalized === "generate" || normalized === "custom" || normalized === "extend") {
103
+ return normalized;
104
+ }
105
+ if (normalized === "custom_generate" || normalized === "custom-generate") return "custom";
106
+ if (normalized === "extend_audio" || normalized === "extend-audio") return "extend";
107
+ return null;
108
+ }
109
+ function inferSubaction(message, params) {
110
+ var _a;
111
+ const explicit = normalizeSubaction(params.action ?? params.subaction ?? params.operation);
112
+ if (explicit) return explicit;
113
+ const text = (((_a = message.content) == null ? void 0 : _a.text) ?? "").toLowerCase();
114
+ if (params.audio_id || /\b(extend|lengthen|longer|add \d+.*seconds?)\b/.test(text)) {
115
+ return "extend";
116
+ }
117
+ if (params.reference_audio || params.style || params.bpm || params.key || params.mode || /\b(custom|style|bpm|key|mode|reference)\b/.test(text)) {
118
+ return "custom";
119
+ }
120
+ return "generate";
121
+ }
122
+ function promptFromParams(message, params) {
123
+ var _a;
124
+ const prompt = typeof params.prompt === "string" ? params.prompt.trim() : "";
125
+ if (prompt) return prompt;
126
+ return (((_a = message.content) == null ? void 0 : _a.text) ?? "").trim();
127
+ }
128
+ function numberOrDefault(value, fallback) {
129
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
130
+ }
131
+ function generationBody(params, prompt) {
132
+ return {
133
+ prompt,
134
+ duration: numberOrDefault(params.duration, 30),
135
+ temperature: numberOrDefault(params.temperature, 1),
136
+ top_k: numberOrDefault(params.topK, 250),
137
+ top_p: numberOrDefault(params.topP, 0.95),
138
+ classifier_free_guidance: numberOrDefault(params.classifier_free_guidance, 3)
139
+ };
140
+ }
141
+ var musicGeneration = {
142
+ name: "MUSIC_GENERATION",
143
+ contexts: ["media"],
144
+ contextGate: { anyOf: ["media"] },
145
+ roleGate: { minRole: "USER" },
146
+ description: "Generate music through Suno. Use action generate for a simple prompt, custom for style/BPM/key/reference parameters, or extend for an existing audio_id and duration.",
147
+ descriptionCompressed: "Suno music generation router action: generate, custom, extend.",
41
148
  similes: [
149
+ "GENERATE_MUSIC",
42
150
  "CREATE_MUSIC",
43
151
  "MAKE_MUSIC",
44
152
  "COMPOSE_MUSIC",
45
- "GENERATE_AUDIO",
46
- "CREATE_SONG",
47
- "MAKE_SONG"
153
+ "CUSTOM_GENERATE_MUSIC",
154
+ "EXTEND_AUDIO"
48
155
  ],
49
- validate: async (runtime, _message) => {
50
- return !!runtime.getSetting("SUNO_API_KEY");
51
- },
52
- handler: async (runtime, message, state, _options, callback) => {
53
- try {
54
- const provider = await SunoProvider.get(runtime, message, state);
55
- const content = message.content;
56
- if (!content.prompt) {
57
- throw new Error("Missing required parameter: prompt");
58
- }
59
- const response = await provider.request("/generate", {
60
- method: "POST",
61
- body: JSON.stringify({
62
- prompt: content.prompt,
63
- duration: content.duration || 30,
64
- temperature: content.temperature || 1,
65
- top_k: content.topK || 250,
66
- top_p: content.topP || 0.95,
67
- classifier_free_guidance: content.classifier_free_guidance || 3
68
- })
69
- });
70
- if (callback) {
71
- callback({
72
- text: "Successfully generated music based on your prompt",
73
- content: response
74
- });
75
- }
76
- return true;
77
- } catch (error) {
78
- if (callback) {
79
- callback({
80
- text: `Failed to extend audio: ${error.message}`,
81
- error
82
- });
83
- }
84
- return false;
156
+ parameters: [
157
+ {
158
+ name: "action",
159
+ description: "Suno operation: generate, custom, or extend.",
160
+ required: false,
161
+ schema: { type: "string", enum: ["generate", "custom", "extend"] }
162
+ },
163
+ {
164
+ name: "subaction",
165
+ description: "Legacy alias for action.",
166
+ required: false,
167
+ schema: { type: "string" }
168
+ },
169
+ {
170
+ name: "prompt",
171
+ description: "Music prompt for generate/custom.",
172
+ required: false,
173
+ schema: { type: "string" }
174
+ },
175
+ {
176
+ name: "audio_id",
177
+ description: "Existing Suno audio id for extend.",
178
+ required: false,
179
+ schema: { type: "string" }
180
+ },
181
+ {
182
+ name: "duration",
183
+ description: "Generation duration or extension seconds.",
184
+ required: false,
185
+ schema: { type: "number", default: 30 }
85
186
  }
86
- },
87
- examples: [
88
- [
89
- {
90
- user: "{{user1}}",
91
- content: {
92
- text: "Create a happy and energetic song",
93
- prompt: "A cheerful and energetic melody with upbeat rhythm",
94
- duration: 30,
95
- temperature: 1
96
- }
97
- },
98
- {
99
- user: "{{agent}}",
100
- content: {
101
- text: "I'll generate a happy and energetic song for you.",
102
- action: "generate-music"
103
- }
104
- },
105
- {
106
- user: "{{agent}}",
107
- content: {
108
- text: "Successfully generated your upbeat and energetic song."
109
- }
110
- }
111
- ],
112
- [
113
- {
114
- user: "{{user1}}",
115
- content: {
116
- text: "Generate a relaxing ambient track",
117
- prompt: "A peaceful ambient soundscape with gentle waves and soft pads",
118
- duration: 45,
119
- temperature: 0.8,
120
- classifier_free_guidance: 4
121
- }
122
- },
123
- {
124
- user: "{{agent}}",
125
- content: {
126
- text: "I'll create a calming ambient piece for you.",
127
- action: "generate-music"
128
- }
129
- },
130
- {
131
- user: "{{agent}}",
132
- content: {
133
- text: "Successfully generated your relaxing ambient soundscape."
134
- }
135
- }
136
- ],
137
- [
138
- {
139
- user: "{{user1}}",
140
- content: {
141
- text: "Make a short jingle for my podcast",
142
- prompt: "A catchy and professional podcast intro jingle",
143
- duration: 15,
144
- temperature: 1.2,
145
- top_k: 300
146
- }
147
- },
148
- {
149
- user: "{{agent}}",
150
- content: {
151
- text: "I'll generate a podcast jingle for you.",
152
- action: "generate-music"
153
- }
154
- },
155
- {
156
- user: "{{agent}}",
157
- content: {
158
- text: "Successfully generated your podcast jingle."
159
- }
160
- }
161
- ]
162
- ]
163
- };
164
- var generate_default = generateMusic;
165
-
166
- // src/actions/customGenerate.ts
167
- var customGenerateMusic = {
168
- name: "custom-generate-music",
169
- description: "Generate music with custom parameters using Suno AI",
170
- similes: [
171
- "CREATE_CUSTOM_MUSIC",
172
- "GENERATE_CUSTOM_AUDIO",
173
- "MAKE_CUSTOM_MUSIC",
174
- "COMPOSE_CUSTOM_MUSIC",
175
- "COMPOSE_MUSIC",
176
- "CREATE_MUSIC",
177
- "GENERATE_MUSIC"
178
187
  ],
179
- validate: async (runtime, _message) => {
180
- return !!runtime.getSetting("SUNO_API_KEY");
188
+ validate: async (runtime, message) => {
189
+ var _a;
190
+ if (!runtime.getSetting("SUNO_API_KEY")) return false;
191
+ const text = (((_a = message.content) == null ? void 0 : _a.text) ?? "").toLowerCase();
192
+ return /\b(generate|create|make|compose|extend|music|song|audio|track)\b/.test(text);
181
193
  },
182
- handler: async (runtime, message, state, _options, callback) => {
194
+ handler: async (runtime, message, state, options, callback) => {
183
195
  try {
196
+ const params = paramsFromMessageAndOptions(message, options);
197
+ const subaction = inferSubaction(message, params);
184
198
  const provider = await SunoProvider.get(runtime, message, state);
185
- const content = message.content;
186
- if (!content.prompt) {
187
- throw new Error("Missing required parameter: prompt");
188
- }
189
- const response = await provider.request("/custom-generate", {
190
- method: "POST",
191
- body: JSON.stringify({
192
- prompt: content.prompt,
193
- duration: content.duration || 30,
194
- temperature: content.temperature || 1,
195
- top_k: content.topK || 250,
196
- top_p: content.topP || 0.95,
197
- classifier_free_guidance: content.classifier_free_guidance || 3,
198
- reference_audio: content.reference_audio,
199
- style: content.style,
200
- bpm: content.bpm,
201
- key: content.key,
202
- mode: content.mode
203
- })
204
- });
205
- if (callback) {
206
- callback({
207
- text: "Successfully generated custom music",
208
- content: response
209
- });
210
- }
211
- return true;
212
- } catch (error) {
213
- if (callback) {
214
- callback({
215
- text: `Failed to generate custom music: ${error.message}`,
216
- error
217
- });
218
- }
219
- return false;
220
- }
221
- },
222
- examples: [
223
- [
224
- {
225
- user: "{{user1}}",
226
- content: {
227
- text: "Create an upbeat electronic dance track with heavy bass",
228
- prompt: "An upbeat electronic dance track with heavy bass and energetic synths",
229
- duration: 60,
230
- style: "electronic",
231
- bpm: 128
232
- }
233
- },
234
- {
235
- user: "{{agent}}",
236
- content: {
237
- text: "I'll generate an energetic EDM track for you.",
238
- action: "custom-generate-music"
239
- }
240
- },
241
- {
242
- user: "{{agent}}",
243
- content: {
244
- text: "Successfully generated your EDM track with heavy bass and synths."
245
- }
246
- }
247
- ],
248
- [
249
- {
250
- user: "{{user1}}",
251
- content: {
252
- text: "Generate a calm piano melody in C major",
253
- prompt: "A gentle, flowing piano melody with soft dynamics",
254
- duration: 45,
255
- style: "classical",
256
- key: "C",
257
- mode: "major",
258
- temperature: 0.8
259
- }
260
- },
261
- {
262
- user: "{{agent}}",
263
- content: {
264
- text: "I'll create a calming piano piece in C major for you.",
265
- action: "custom-generate-music"
266
- }
267
- },
268
- {
269
- user: "{{agent}}",
270
- content: {
271
- text: "Successfully generated your peaceful piano melody in C major."
272
- }
273
- }
274
- ],
275
- [
276
- {
277
- user: "{{user1}}",
278
- content: {
279
- text: "Make a rock song with guitar solos",
280
- prompt: "A rock song with powerful electric guitar solos and driving drums",
281
- duration: 90,
282
- style: "rock",
283
- bpm: 120,
284
- classifier_free_guidance: 4
199
+ let endpoint = "/generate";
200
+ let body;
201
+ if (subaction === "extend") {
202
+ if (!params.audio_id || !params.duration) {
203
+ throw new Error("Missing required parameters: audio_id and duration");
285
204
  }
286
- },
287
- {
288
- user: "{{agent}}",
289
- content: {
290
- text: "I'll generate a rock track with guitar solos for you.",
291
- action: "custom-generate-music"
205
+ endpoint = "/extend";
206
+ body = {
207
+ audio_id: params.audio_id,
208
+ duration: params.duration
209
+ };
210
+ } else {
211
+ const prompt = promptFromParams(message, params);
212
+ if (!prompt) {
213
+ throw new Error("Missing required parameter: prompt");
292
214
  }
293
- },
294
- {
295
- user: "{{agent}}",
296
- content: {
297
- text: "Successfully generated your rock song with guitar solos."
215
+ body = generationBody(params, prompt);
216
+ if (subaction === "custom") {
217
+ endpoint = "/custom-generate";
218
+ body = {
219
+ ...body,
220
+ reference_audio: params.reference_audio,
221
+ style: params.style,
222
+ bpm: params.bpm,
223
+ key: params.key,
224
+ mode: params.mode
225
+ };
298
226
  }
299
227
  }
300
- ]
301
- ]
302
- };
303
- var customGenerate_default = customGenerateMusic;
304
-
305
- // src/actions/extend.ts
306
- var extendAudio = {
307
- name: "extend-audio",
308
- description: "Extend the duration of an existing audio generation",
309
- similes: [
310
- "LENGTHEN_AUDIO",
311
- "PROLONG_AUDIO",
312
- "INCREASE_DURATION",
313
- "MAKE_AUDIO_LONGER"
314
- ],
315
- validate: async (runtime, _message) => {
316
- return !!runtime.getSetting("SUNO_API_KEY");
317
- },
318
- handler: async (runtime, message, state, _options, callback) => {
319
- try {
320
- const provider = await SunoProvider.get(runtime, message, state);
321
- const content = message.content;
322
- if (!content.audio_id || !content.duration) {
323
- throw new Error("Missing required parameters: audio_id and duration");
324
- }
325
- const response = await provider.request("/extend", {
228
+ const controller = new AbortController();
229
+ const timeout = setTimeout(() => controller.abort(), SUNO_ACTION_TIMEOUT_MS);
230
+ const response = await provider.request(runtime, endpoint, {
326
231
  method: "POST",
327
- body: JSON.stringify({
328
- audio_id: content.audio_id,
329
- duration: content.duration
330
- })
331
- });
332
- if (callback) {
333
- callback({
334
- text: `Successfully extended audio ${content.audio_id}`,
335
- content: response
336
- });
337
- }
338
- return true;
232
+ body: JSON.stringify(body),
233
+ signal: controller.signal
234
+ }).finally(() => clearTimeout(timeout));
235
+ const cappedResponse = JSON.stringify(response).length > MAX_SUNO_RESPONSE_BYTES ? {
236
+ truncated: true,
237
+ preview: JSON.stringify(response).slice(0, MAX_SUNO_RESPONSE_BYTES)
238
+ } : response;
239
+ await (callback == null ? void 0 : callback({
240
+ text: subaction === "extend" ? `Successfully extended audio ${params.audio_id}` : `Successfully submitted ${subaction} music generation`,
241
+ content: cappedResponse
242
+ }));
243
+ return {
244
+ success: true,
245
+ text: subaction === "extend" ? `Successfully extended audio ${params.audio_id}` : `Successfully submitted ${subaction} music generation`,
246
+ data: { subaction, response: cappedResponse }
247
+ };
339
248
  } catch (error) {
340
- if (callback) {
341
- callback({
342
- text: `Failed to extend audio: ${error.message}`,
343
- error
344
- });
345
- }
346
- return false;
249
+ const errorMessage = error instanceof Error ? error.message : String(error);
250
+ const text = `Music generation failed: ${errorMessage}`;
251
+ await (callback == null ? void 0 : callback({
252
+ text,
253
+ error
254
+ }));
255
+ return { success: false, text, error: errorMessage };
347
256
  }
348
257
  },
349
258
  examples: [
350
259
  [
351
260
  {
352
- user: "{{user1}}",
353
- content: {
354
- text: "Make this song longer by 30 seconds",
355
- audio_id: "abc123",
356
- duration: 30
357
- }
358
- },
359
- {
360
- user: "{{agent}}",
361
- content: {
362
- text: "I'll extend your song by 30 seconds.",
363
- action: "extend-audio"
364
- }
365
- },
366
- {
367
- user: "{{agent}}",
368
- content: {
369
- text: "Successfully extended your song by 30 seconds."
370
- }
371
- }
372
- ],
373
- [
374
- {
375
- user: "{{user1}}",
376
- content: {
377
- text: "Double the length of this track",
378
- audio_id: "xyz789",
379
- duration: 60
380
- }
381
- },
382
- {
383
- user: "{{agent}}",
384
- content: {
385
- text: "I'll double the duration of your track.",
386
- action: "extend-audio"
387
- }
388
- },
389
- {
390
- user: "{{agent}}",
391
- content: {
392
- text: "Successfully doubled the length of your track to 60 seconds."
393
- }
394
- }
395
- ],
396
- [
397
- {
398
- user: "{{user1}}",
399
- content: {
400
- text: "Add 15 more seconds to this melody",
401
- audio_id: "def456",
402
- duration: 15
403
- }
404
- },
405
- {
406
- user: "{{agent}}",
261
+ name: "{{user1}}",
407
262
  content: {
408
- text: "I'll add 15 seconds to your melody.",
409
- action: "extend-audio"
263
+ text: "Generate a relaxing ambient track",
264
+ prompt: "A peaceful ambient soundscape with gentle waves and soft pads",
265
+ duration: 45
410
266
  }
411
267
  },
412
268
  {
413
- user: "{{agent}}",
269
+ name: "{{agent}}",
414
270
  content: {
415
- text: "Successfully added 15 seconds to your melody."
271
+ text: "I'll generate a calming ambient piece.",
272
+ action: "MUSIC_GENERATION"
416
273
  }
417
274
  }
418
275
  ]
419
276
  ]
420
277
  };
421
- var extend_default = extendAudio;
278
+ var musicGeneration_default = musicGeneration;
422
279
 
423
280
  // src/index.ts
424
281
  var sunoPlugin = {
425
282
  name: "suno",
426
283
  description: "Suno AI Music Generation Plugin for Eliza",
427
- actions: [generate_default, customGenerate_default, extend_default],
428
- evaluators: [],
429
- providers: [SunoProvider]
284
+ actions: [musicGeneration_default],
285
+ providers: [sunoStatusProvider],
286
+ // Self-declared auto-enable: activate when SUNO_API_KEY is set OR when
287
+ // media.audio is configured to use the suno provider with own-key mode.
288
+ autoEnable: {
289
+ shouldEnable: (env, config) => {
290
+ const key = env.SUNO_API_KEY;
291
+ if (typeof key === "string" && key.trim() !== "") return true;
292
+ const media = config == null ? void 0 : config.media;
293
+ const audio = media == null ? void 0 : media.audio;
294
+ return Boolean(
295
+ audio && audio.enabled !== false && audio.mode === "own-key" && audio.provider === "suno"
296
+ );
297
+ }
298
+ }
430
299
  };
431
300
  var index_default = sunoPlugin;
432
301
  export {
433
- customGenerate_default as CustomGenerateMusic,
434
- extend_default as ExtendAudio,
435
- generate_default as GenerateMusic,
302
+ musicGeneration_default as CustomGenerateMusic,
303
+ musicGeneration_default as ExtendAudio,
304
+ musicGeneration_default as GenerateMusic,
305
+ musicGeneration_default as MusicGeneration,
436
306
  SunoProvider,
437
307
  index_default as default,
438
- sunoPlugin
308
+ sunoPlugin,
309
+ sunoStatusProvider
439
310
  };
440
311
  //# sourceMappingURL=index.js.map