Skip to content

Commit 8884ff9

Browse files
authored
Merge branch 'develop' into add-livepeer-image-gen-1
2 parents 74f8984 + d371fee commit 8884ff9

File tree

12 files changed

+352
-127
lines changed

12 files changed

+352
-127
lines changed

.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ DISCORD_VOICE_CHANNEL_ID= # The ID of the voice channel the bot should joi
99

1010
# AI Model API Keys
1111
OPENAI_API_KEY= # OpenAI API key, starting with sk-
12+
OPENAI_API_URL= # OpenAI API Endpoint (optional), Default: https://api.openai.com/v1
1213
SMALL_OPENAI_MODEL= # Default: gpt-4o-mini
1314
MEDIUM_OPENAI_MODEL= # Default: gpt-4o
1415
LARGE_OPENAI_MODEL= # Default: gpt-4o

packages/client-direct/src/index.ts

+157
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,163 @@ export class DirectClient {
445445
}
446446
}
447447
);
448+
449+
this.app.post("/:agentId/speak", async (req, res) => {
450+
const agentId = req.params.agentId;
451+
const roomId = stringToUuid(req.body.roomId ?? "default-room-" + agentId);
452+
const userId = stringToUuid(req.body.userId ?? "user");
453+
const text = req.body.text;
454+
455+
if (!text) {
456+
res.status(400).send("No text provided");
457+
return;
458+
}
459+
460+
let runtime = this.agents.get(agentId);
461+
462+
// if runtime is null, look for runtime with the same name
463+
if (!runtime) {
464+
runtime = Array.from(this.agents.values()).find(
465+
(a) => a.character.name.toLowerCase() === agentId.toLowerCase()
466+
);
467+
}
468+
469+
if (!runtime) {
470+
res.status(404).send("Agent not found");
471+
return;
472+
}
473+
474+
try {
475+
// Process message through agent (same as /message endpoint)
476+
await runtime.ensureConnection(
477+
userId,
478+
roomId,
479+
req.body.userName,
480+
req.body.name,
481+
"direct"
482+
);
483+
484+
const messageId = stringToUuid(Date.now().toString());
485+
486+
const content: Content = {
487+
text,
488+
attachments: [],
489+
source: "direct",
490+
inReplyTo: undefined,
491+
};
492+
493+
const userMessage = {
494+
content,
495+
userId,
496+
roomId,
497+
agentId: runtime.agentId,
498+
};
499+
500+
const memory: Memory = {
501+
id: messageId,
502+
agentId: runtime.agentId,
503+
userId,
504+
roomId,
505+
content,
506+
createdAt: Date.now(),
507+
};
508+
509+
await runtime.messageManager.createMemory(memory);
510+
511+
const state = await runtime.composeState(userMessage, {
512+
agentName: runtime.character.name,
513+
});
514+
515+
const context = composeContext({
516+
state,
517+
template: messageHandlerTemplate,
518+
});
519+
520+
const response = await generateMessageResponse({
521+
runtime: runtime,
522+
context,
523+
modelClass: ModelClass.LARGE,
524+
});
525+
526+
// save response to memory
527+
const responseMessage = {
528+
...userMessage,
529+
userId: runtime.agentId,
530+
content: response,
531+
};
532+
533+
await runtime.messageManager.createMemory(responseMessage);
534+
535+
if (!response) {
536+
res.status(500).send("No response from generateMessageResponse");
537+
return;
538+
}
539+
540+
let message = null as Content | null;
541+
542+
await runtime.evaluate(memory, state);
543+
544+
const _result = await runtime.processActions(
545+
memory,
546+
[responseMessage],
547+
state,
548+
async (newMessages) => {
549+
message = newMessages;
550+
return [memory];
551+
}
552+
);
553+
554+
// Get the text to convert to speech
555+
const textToSpeak = response.text;
556+
557+
// Convert to speech using ElevenLabs
558+
const elevenLabsApiUrl = `https://api.elevenlabs.io/v1/text-to-speech/${process.env.ELEVENLABS_VOICE_ID}`;
559+
const apiKey = process.env.ELEVENLABS_XI_API_KEY;
560+
561+
if (!apiKey) {
562+
throw new Error("ELEVENLABS_XI_API_KEY not configured");
563+
}
564+
565+
const speechResponse = await fetch(elevenLabsApiUrl, {
566+
method: "POST",
567+
headers: {
568+
"Content-Type": "application/json",
569+
"xi-api-key": apiKey,
570+
},
571+
body: JSON.stringify({
572+
text: textToSpeak,
573+
model_id: process.env.ELEVENLABS_MODEL_ID || "eleven_multilingual_v2",
574+
voice_settings: {
575+
stability: parseFloat(process.env.ELEVENLABS_VOICE_STABILITY || "0.5"),
576+
similarity_boost: parseFloat(process.env.ELEVENLABS_VOICE_SIMILARITY_BOOST || "0.9"),
577+
style: parseFloat(process.env.ELEVENLABS_VOICE_STYLE || "0.66"),
578+
use_speaker_boost: process.env.ELEVENLABS_VOICE_USE_SPEAKER_BOOST === "true",
579+
},
580+
}),
581+
});
582+
583+
if (!speechResponse.ok) {
584+
throw new Error(`ElevenLabs API error: ${speechResponse.statusText}`);
585+
}
586+
587+
const audioBuffer = await speechResponse.arrayBuffer();
588+
589+
// Set appropriate headers for audio streaming
590+
res.set({
591+
'Content-Type': 'audio/mpeg',
592+
'Transfer-Encoding': 'chunked'
593+
});
594+
595+
res.send(Buffer.from(audioBuffer));
596+
597+
} catch (error) {
598+
console.error("Error processing message or generating speech:", error);
599+
res.status(500).json({
600+
error: "Error processing message or generating speech",
601+
details: error.message
602+
});
603+
}
604+
});
448605
}
449606

450607
// agent/src/index.ts:startAgent calls this

packages/client-github/src/index.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,8 @@ export class GitHubClient {
8282
`Successfully cloned repository from ${repositoryUrl}`
8383
);
8484
return;
85-
} catch (error) {
86-
elizaLogger.error(
87-
`Failed to clone repository from ${repositoryUrl}. Retrying...`,
88-
error
89-
);
85+
} catch {
86+
elizaLogger.error(`Failed to clone repository from ${repositoryUrl}. Retrying...`);
9087
retries++;
9188
if (retries === maxRetries) {
9289
throw new Error(

packages/client-twitter/src/base.ts

+12-13
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
Tweet,
1717
} from "agent-twitter-client";
1818
import { EventEmitter } from "events";
19+
import { TwitterConfig } from "./environment.ts";
1920

2021
export function extractAnswer(text: string): string {
2122
const startIndex = text.indexOf("Answer: ") + 8;
@@ -85,6 +86,7 @@ export class ClientBase extends EventEmitter {
8586
static _twitterClients: { [accountIdentifier: string]: Scraper } = {};
8687
twitterClient: Scraper;
8788
runtime: IAgentRuntime;
89+
twitterConfig: TwitterConfig;
8890
directions: string;
8991
lastCheckedTweetId: bigint | null = null;
9092
imageDescriptionService: IImageDescriptionService;
@@ -134,10 +136,11 @@ export class ClientBase extends EventEmitter {
134136
);
135137
}
136138

137-
constructor(runtime: IAgentRuntime) {
139+
constructor(runtime: IAgentRuntime, twitterConfig:TwitterConfig) {
138140
super();
139141
this.runtime = runtime;
140-
const username = this.runtime.getSetting("TWITTER_USERNAME");
142+
this.twitterConfig = twitterConfig;
143+
const username = twitterConfig.TWITTER_USERNAME;
141144
if (ClientBase._twitterClients[username]) {
142145
this.twitterClient = ClientBase._twitterClients[username];
143146
} else {
@@ -153,15 +156,11 @@ export class ClientBase extends EventEmitter {
153156
}
154157

155158
async init() {
156-
const username = this.runtime.getSetting("TWITTER_USERNAME");
157-
const password = this.runtime.getSetting("TWITTER_PASSWORD");
158-
const email = this.runtime.getSetting("TWITTER_EMAIL");
159-
let retries = parseInt(
160-
this.runtime.getSetting("TWITTER_RETRY_LIMIT") || "5",
161-
10
162-
);
163-
const twitter2faSecret =
164-
this.runtime.getSetting("TWITTER_2FA_SECRET") || undefined;
159+
const username = this.twitterConfig.TWITTER_USERNAME;
160+
const password = this.twitterConfig.TWITTER_PASSWORD;
161+
const email = this.twitterConfig.TWITTER_EMAIL;
162+
let retries = this.twitterConfig.TWITTER_RETRY_LIMIT
163+
const twitter2faSecret = this.twitterConfig.TWITTER_2FA_SECRET;
165164

166165
if (!username) {
167166
throw new Error("Twitter username not configured");
@@ -314,7 +313,7 @@ export class ClientBase extends EventEmitter {
314313
async fetchTimelineForActions(count: number): Promise<Tweet[]> {
315314
elizaLogger.debug("fetching timeline for actions");
316315

317-
const agentUsername = this.runtime.getSetting("TWITTER_USERNAME");
316+
const agentUsername = this.twitterConfig.TWITTER_USERNAME
318317
const homeTimeline = await this.twitterClient.fetchHomeTimeline(
319318
count,
320319
[]
@@ -510,7 +509,7 @@ export class ClientBase extends EventEmitter {
510509
}
511510

512511
const timeline = await this.fetchHomeTimeline(cachedTimeline ? 10 : 50);
513-
const username = this.runtime.getSetting("TWITTER_USERNAME");
512+
const username = this.twitterConfig.TWITTER_USERNAME;
514513

515514
// Get the most recent 20 mentions and interactions
516515
const mentionsAndInteractions = await this.fetchSearchTweets(

0 commit comments

Comments
 (0)