Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugin-beatsfoundation): implement feedback from PR #2452 #2552

Open
wants to merge 30 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a5cc630
add media creation engine
beatsfoundation Jan 17, 2025
2431ee2
Merge branch 'develop' into develop
wtfsayo Jan 19, 2025
c7ff3d6
Merge branch 'develop' into develop
wtfsayo Jan 19, 2025
66ee7b2
Merge branch 'develop' into develop
wtfsayo Jan 20, 2025
f3859d2
refactor: replace generateObjectDeprecated with generateObject
devin-ai-integration[bot] Jan 20, 2025
643c439
feat(plugin-beatsfoundation): add request cancellation support to Cre…
devin-ai-integration[bot] Jan 20, 2025
fef1cbc
refactor(plugin-beatsfoundation): extract shared error handling into …
devin-ai-integration[bot] Jan 20, 2025
b9b7d30
feat(plugin-beatsfoundation): add pagination metadata and improve val…
devin-ai-integration[bot] Jan 20, 2025
b01b839
feat(plugin-beatsfoundation): add input sanitization for CreateSong c…
devin-ai-integration[bot] Jan 20, 2025
120dfed
feat(plugin-beatsfoundation): sanitize sensitive information in Creat…
devin-ai-integration[bot] Jan 20, 2025
46c214c
feat(plugin-beatsfoundation): add request cancellation and error hand…
devin-ai-integration[bot] Jan 20, 2025
342c395
Merge branch 'develop' into devin/1737374423-replace-deprecated-gener…
wtfsayo Jan 20, 2025
e6bd52c
feat(plugin-beatsfoundation): add request cancellation support to Cre…
devin-ai-integration[bot] Jan 20, 2025
fd30a32
refactor(plugin-beatsfoundation): extract shared error handling into …
devin-ai-integration[bot] Jan 20, 2025
f5273e0
feat(plugin-beatsfoundation): add pagination metadata and improve val…
devin-ai-integration[bot] Jan 20, 2025
65f9b4e
feat(plugin-beatsfoundation): add input sanitization for CreateSong c…
devin-ai-integration[bot] Jan 20, 2025
9a51084
feat(plugin-beatsfoundation): sanitize sensitive information in Creat…
devin-ai-integration[bot] Jan 20, 2025
8d0ab49
feat(plugin-beatsfoundation): add request cancellation and error hand…
devin-ai-integration[bot] Jan 20, 2025
1b7955a
Merge branch 'beats-foundation-updates' into devin/1737374423-replace…
devin-ai-integration[bot] Jan 20, 2025
e22dce8
Merge branch 'develop' into devin/1737374423-replace-deprecated-gener…
wtfsayo Jan 20, 2025
6c31b3d
fix(plugin-beatsfoundation): wrap examples in ActionExample[][] array…
devin-ai-integration[bot] Jan 20, 2025
0a08c68
fix: add plugin-beatsfoundation to agent dependencies
devin-ai-integration[bot] Jan 20, 2025
8c57de2
build: update plugin-beatsfoundation to use tsup for building
devin-ai-integration[bot] Jan 20, 2025
2573b9c
build: update plugin-beatsfoundation tsconfig.json to match core ESM …
devin-ai-integration[bot] Jan 20, 2025
9787a74
Merge branch 'develop' into devin/1737374423-replace-deprecated-gener…
wtfsayo Jan 20, 2025
5dd75bc
build: update plugin-beatsfoundation tsconfig.json to use NodeNext mo…
devin-ai-integration[bot] Jan 20, 2025
2e96789
build: add plugin-beatsfoundation to turbo build pipeline
devin-ai-integration[bot] Jan 20, 2025
9f64f41
Merge branch 'develop' into devin/1737374423-replace-deprecated-gener…
wtfsayo Jan 20, 2025
d60b76f
build: update plugin-beatsfoundation ESM configuration
devin-ai-integration[bot] Jan 20, 2025
7dc5cb5
fix: update imports to use .js extensions consistently
devin-ai-integration[bot] Jan 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"@elizaos/plugin-pyth-data": "workspace:*",
"@elizaos/plugin-openai": "workspace:*",
"@elizaos/plugin-devin": "workspace:*",
"@elizaos/plugin-beatsfoundation": "workspace:*",
"readline": "1.3.0",
"ws": "8.18.0",
"yargs": "17.7.2"
Expand Down
2 changes: 2 additions & 0 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { autonomePlugin } from "@elizaos/plugin-autonome";
import { availPlugin } from "@elizaos/plugin-avail";
import { avalanchePlugin } from "@elizaos/plugin-avalanche";
import { b2Plugin } from "@elizaos/plugin-b2";
import { beatsfoundationPlugin } from "@elizaos/plugin-beatsfoundation";
import { binancePlugin } from "@elizaos/plugin-binance";
import { birdeyePlugin } from "@elizaos/plugin-birdeye";
import {
Expand Down Expand Up @@ -1002,6 +1003,7 @@ export async function createAgent(
? abstractPlugin
: null,
getSecret(character, "B2_PRIVATE_KEY") ? b2Plugin : null,
getSecret(character, "BEATSFOUNDATION_API_KEY") ? beatsfoundationPlugin : null,
getSecret(character, "BINANCE_API_KEY") &&
getSecret(character, "BINANCE_SECRET_KEY")
? binancePlugin
Expand Down
118 changes: 118 additions & 0 deletions packages/plugin-beatsfoundation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# @elizaos/plugin-beatsfoundation

A plugin for Eliza that enables AI music generation using the Beats Foundation API.

## Features
- AI-powered music generation from text prompts
- Support for multiple genres and moods
- Optional lyrics input
- Instrumental track generation
- Natural language processing for music generation requests
- Access to the Beats Foundation song library

## Installation
```bash
npm install @elizaos/plugin-beatsfoundation
```

## Configuration
1. Get your API key from [Beats Foundation](https://www.beatsfoundation.com)
2. Set up your environment variables:
```bash
BEATS_FOUNDATION_API_KEY=your_api_key
```
3. Register the plugin in your Eliza configuration:
```typescript
import { BeatsFoundationPlugin } from "@elizaos/plugin-beatsfoundation";
// In your Eliza configuration
plugins: [
new BeatsFoundationPlugin(),
// ... other plugins
];
```

## Usage
The plugin responds to natural language queries for music generation. Here are some examples:
```plaintext
"Generate a happy pop song about summer"
"Create an instrumental jazz track"
"Make me a rock song with these lyrics: [lyrics]"
"List recent AI-generated songs"
```

### Supported Parameters
The plugin supports various music generation parameters including:
- Genre (pop, rock, jazz, etc.)
- Mood (happy, sad, energetic, etc.)
- Lyrics (optional)
- Instrumental toggle (boolean, will generate instrumental track or with vocals)
- Custom prompts (up to 200 characters)

### Available Actions
#### GENERATE_SONG
Generates a new AI song based on provided parameters.
```typescript
// Example response format
{
id: "song_123",
title: "Summer Vibes",
audio_url: "https://...",
streams: 0,
upvote_count: 0,
song_url: "https://...",
username: "user123"
}
```

#### LIST_SONGS
Retrieves a paginated list of generated songs.

## API Reference
For detailed API documentation, visit [docs.beatsfoundation.com](https://docs.beatsfoundation.com)

### Environment Variables
| Variable | Description | Required |
| -------- | ----------- | -------- |
| BEATS_FOUNDATION_API_KEY | Your Beats Foundation API key | Yes |

### Types
```typescript
interface GenerateSongRequest {
prompt: string;
lyrics?: string;
genre?: string;
mood?: string;
isInstrumental?: boolean;
}

interface Song {
id: string;
title: string;
audio_url: string;
streams: number;
upvote_count: number;
song_url: string;
username: string;
}
```

## Error Handling
The plugin includes comprehensive error handling for:
- Invalid API keys
- Rate limiting (2 generations per hour)
- Network timeouts
- Invalid generation parameters
- Server errors

## Rate Limits
The Beats Foundation API is currently free to use and has a rate limit of 2 song generations per hour per API key. Public endpoints like song listing and retrieval are not rate limited.

## Support
For support, please:
- Visit [docs.beatsfoundation.com](https://docs.beatsfoundation.com)
- Open an issue in the repository
- Join our Discord community

## Links
- [Beats Foundation API Documentation](https://docs.beatsfoundation.com)
- [GitHub Repository](https://github.com/elizaos/eliza/tree/main/packages/plugin-beatsfoundation)
33 changes: 33 additions & 0 deletions packages/plugin-beatsfoundation/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@elizaos/plugin-beatsfoundation",
"version": "0.0.1",
"description": "Beats Foundation plugin for ElizaOS",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsup --format esm --dts",
"clean": "rimraf dist",
"dev": "tsup --format esm --dts --watch",
"lint": "eslint src --ext .ts",
"test": "jest"
},
"dependencies": {
"@elizaos/core": "workspace:*",
"axios": "^1.6.7"
},
"devDependencies": {
"@types/node": "^20.11.19",
"typescript": "^5.3.3",
"tsup": "^8.0.0"
},
"peerDependencies": {
"@elizaos/core": "workspace:*"
}
}
35 changes: 35 additions & 0 deletions packages/plugin-beatsfoundation/src/actions/CreateSong/examples.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ActionExample } from "@elizaos/core";

export const createSongExamples: ActionExample[][] = [
[
{
input: "Create a happy pop song about summer",
output: {
prompt: "Create a happy pop song about summer",
genre: "pop",
mood: "happy"
}
}
],
[
{
input: "Generate an instrumental jazz piece with a relaxing vibe",
output: {
prompt: "Generate an instrumental jazz piece with a relaxing vibe",
genre: "jazz",
mood: "relaxing",
isInstrumental: true
}
}
],
[
{
input: "Make a rock song with these lyrics: Life is a highway, I wanna ride it all night long",
output: {
prompt: "Make a rock song",
genre: "rock",
lyrics: "Life is a highway, I wanna ride it all night long"
}
}
]
];
115 changes: 115 additions & 0 deletions packages/plugin-beatsfoundation/src/actions/CreateSong/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {
composeContext,
elizaLogger,
generateObject,
HandlerCallback,
IAgentRuntime,
Memory,
ModelClass,
State,
type Action,
} from "@elizaos/core";
import axios from 'axios';
import { validateBeatsFoundationConfig } from "../../environment.js";
import { sanitizeCreateSongContent } from "../../utils/content-sanitizer.js";
import { createSafeResponse } from "../../utils/response-sanitizer.js";
import { createSongExamples } from "./examples.js";
import { createSongService } from "./service.js";
import { createSongTemplate } from "./template.js";
import { CreateSongContent } from "./types.js";
import { isCreateSongContent } from "./validation.js";

export default {
name: "CREATE_SONG",
similes: ["GENERATE_SONG", "MAKE_SONG", "COMPOSE_SONG"],
validate: async (runtime: IAgentRuntime, _message: Memory) => {
await validateBeatsFoundationConfig(runtime);
return true;
},
description: "Create a new song using Beats Foundation",
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
_options: { [key: string]: unknown },
callback?: HandlerCallback
): Promise<boolean> => {
elizaLogger.log("Starting Beats Foundation CREATE_SONG handler...");

// Initialize or update state
if (!state) {
state = (await runtime.composeState(message)) as State;
} else {
state = await runtime.updateRecentMessageState(state);
}

try {
// Compose and generate content
const context = composeContext({
state,
template: createSongTemplate,
});

const content = (await generateObject({
runtime,
context,
modelClass: ModelClass.SMALL,
})) as unknown as CreateSongContent;

// Validate and sanitize content
if (!isCreateSongContent(content)) {
throw new Error("Invalid song creation content");
}

// Sanitize content
const sanitizedContent = sanitizeCreateSongContent(content);

// Get config with validation
const config = await validateBeatsFoundationConfig(runtime);
const songService = createSongService(config.BEATSFOUNDATION_API_KEY);

try {
// Create cancel token for request cancellation
const source = axios.CancelToken.source();
const song = await songService.createSong(sanitizedContent, { cancelToken: source.token });
elizaLogger.success(
`Song created successfully! Title: ${song.title}`
);

if (callback) {
callback({
text: `Created new song: ${song.title}`,
content: createSafeResponse(
song,
Boolean(sanitizedContent.lyrics),
sanitizedContent.genre,
sanitizedContent.mood,
sanitizedContent.isInstrumental
),
});
}

return true;
} catch (error: any) {
elizaLogger.error("Error in CREATE_SONG handler:", error);
if (callback) {
callback({
text: `Error creating song: ${error.message}`,
content: { error: error.message },
});
}
return false;
}
} catch (error: any) {
elizaLogger.error("Error in CREATE_SONG handler:", error);
if (callback) {
callback({
text: `Error creating song: ${error.message}`,
content: { error: error.message },
});
}
return false;
}
},
examples: createSongExamples,
} satisfies Action;
58 changes: 58 additions & 0 deletions packages/plugin-beatsfoundation/src/actions/CreateSong/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import axios, { CancelToken } from 'axios';
import { Song } from '../../types.js';
import { CreateSongContent, CreateSongOptions } from './types.js';

export function createSongService(apiKey: string) {
// Create axios instance with retry configuration
const client = axios.create();

// Will be configured once axios-retry package is added
/*
axiosRetry(client, {
retries: 3,
retryDelay: axiosRetry.exponentialDelay,
retryCondition: (error) => {
return axiosRetry.isNetworkOrIdempotentRequestError(error)
|| error.code === 'ECONNABORTED';
}
});
*/

return {
createSong: async (content: CreateSongContent, options?: CreateSongOptions): Promise<Song> => {
try {
const response = await client.post(
'https://www.beatsfoundation.com/api/songs',
content,
{
...options,
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
timeout: 300000, // 5 minutes timeout for song generation
}
);
return response.data.song;
} catch (error: any) {
// Handle cancellation
if (axios.isCancel(error)) {
throw new Error('Song creation request was cancelled');
}

// Handle API errors
if (error.response) {
throw new Error(`Beats Foundation API Error: ${error.response.data.error || error.response.status}`);
}

// Handle network errors
if (error.code === 'ECONNABORTED') {
throw new Error('Song creation request timed out');
}

// Handle other errors
throw error;
}
}
};
}
Loading
Loading