diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000000..06cd0272520 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v23.1.0 \ No newline at end of file diff --git a/README.md b/README.md index a9abbd61e9a..e2127ddcd70 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ ## 🌍 README Translations -[中文说明](./README_CN.md) | [日本語の説明](./README_JA.md) | [한국어 설명](./README_KOR.md) | [Français](./README_FR.md) | [Português](./README_PTBR.md) | [Türkçe](./README_TR.md) | [Русский](./README_RU.md) | [Español](./README_ES.md) + +[中文说明](./README_CN.md) | [日本語の説明](./README_JA.md) | [한국어 설명](./README_KOR.md) | [Français](./README_FR.md) | [Português](./README_PTBR.md) | [Türkçe](./README_TR.md) | [Русский](./README_RU.md) | [Español](./README_ES.md) | [Italiano](./README_IT.md) ## ✨ Features @@ -40,7 +41,7 @@ - [Node.js 22+](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) - [pnpm](https://pnpm.io/installation) -> **Note for Windows Users:** WSL is required +> **Note for Windows Users:** [WSL 2](https://learn.microsoft.com/en-us/windows/wsl/install-manual) is required. ### Edit the .env file diff --git a/README_IT.md b/README_IT.md new file mode 100644 index 00000000000..ab74ca0ec41 --- /dev/null +++ b/README_IT.md @@ -0,0 +1,92 @@ +# Eliza 🤖 + +
+ Eliza Banner +
+ +## ✨ Caratteristiche + +- 🛠️ Connettori completi per Discord, Twitter e Telegram +- 🔗 Supporto per tutti i modelli (Llama, Grok, OpenAI, Anthropic, ecc.) +- 👥 Supporto multi-agente e per stanze +- 📚 Acquisisci ed interagisci facilmente con i tuoi documenti +- 💾 Memoria recuperabile e archivio documenti +- 🚀 Altamente estensibile - crea le tue azioni e clients personalizzati +- ☁️ Supporto di numerosi modelli (Llama locale, OpenAI, Anthropic, Groq, ecc.) +- 📦 Funziona e basta! + +## 🎯 Casi d'Uso + +- 🤖 Chatbot +- 🕵️ Agenti Autonomi +- 📈 Gestione Processi Aziendali +- 🎮 NPC per Videogiochi +- 🧠 Trading + +## 🚀 Avvio Rapido + +### Prerequisiti + +- [Python 2.7+](https://www.python.org/downloads/) +- [Node.js 22+](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) +- [pnpm](https://pnpm.io/installation) + +> **Nota per gli utenti Windows:** È richiesto WSL + +### Modifica il file .env + +Copia .env.example in .env e inserisci i valori appropriati + +``` +cp .env.example .env +``` + +### Avvia Eliza Automaticamente + +Questo script eseguirà tutti i comandi necessari per configurare il progetto e avviare il bot con il personaggio predefinito. + +```bash +sh scripts/start.sh +``` + +### Modifica il file del personaggio + +1. Apri `packages/agent/src/character.ts` per modificare il personaggio predefinito. Decommentare e modificare. + +2. Per caricare personaggi personalizzati: + - Usa `pnpm start --characters="percorso/del/tuo/personaggio.json"` + - È possibile caricare più file di personaggi contemporaneamente + +### Avvia Eliza Manualmente + +```bash +pnpm i +pnpm build +pnpm start + +# Il progetto evolve rapidamente; a volte è necessario pulire il progetto se si ritorna sul progetto dopo un po' di tempo +pnpm clean +``` + +#### Requisiti Aggiuntivi + +Potrebbe essere necessario installare Sharp. Se vedi un errore all'avvio, prova a installarlo con il seguente comando: + +``` +pnpm install --include=optional sharp +``` + +### Community e contatti + +- [GitHub Issues](https://github.com/ai16z/eliza/issues). Ideale per: bug riscontrati utilizzando Eliza e proposte di funzionalità. +- [Discord](https://discord.gg/ai16z). Ideale per: condividere le tue applicazioni e interagire con la community. + +## Contributori + + + + + +## Cronologia Stelle + +[![Grafico Cronologia Stelle](https://api.star-history.com/svg?repos=ai16z/eliza&type=Date)](https://star-history.com/#ai16z/eliza&Date) \ No newline at end of file diff --git a/docs/docs/packages/adapters.md b/docs/docs/packages/adapters.md index 1ad639f23bc..374dc1d17bc 100644 --- a/docs/docs/packages/adapters.md +++ b/docs/docs/packages/adapters.md @@ -425,9 +425,42 @@ async searchMemories(params: { ### PostgreSQL Schema ```sql --- migrations/20240318103238_remote_schema.sql CREATE EXTENSION IF NOT EXISTS vector; +CREATE TABLE IF NOT EXISTS accounts ( + id UUID PRIMARY KEY, + "createdAt" DEFAULT CURRENT_TIMESTAMP, + "name" TEXT, + "username" TEXT, + "email" TEXT NOT NULL, + "avatarUrl" TEXT, + "details" JSONB DEFAULT '{}'::"jsonb", + "is_agent" BOOLEAN DEFAULT false NOT NULL, + "location" TEXT, + "profile_line" TEXT, + "signed_tos" BOOLEAN DEFAULT false NOT NULL +); + +ALTER TABLE ONLY accounts ADD CONSTRAINT users_email_key UNIQUE (email); + +CREATE TABLE IF NOT EXISTS participants ( + "id" UUID PRIMARY KEY, + "createdAt" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, + "userId" UUID REFERENCES accounts(id), + "roomId" UUID REFERENCES rooms(id), + "userState" TEXT, -- For MUTED, NULL, or FOLLOWED states + "last_message_read" UUID +); + +ALTER TABLE ONLY participants ADD CONSTRAINT participants_id_key UNIQUE (id); +ALTER TABLE ONLY participants ADD CONSTRAINT participants_roomId_fkey FOREIGN KEY ("roomId") REFERENCES rooms(id); +ALTER TABLE ONLY participants ADD CONSTRAINT participants_userId_fkey FOREIGN KEY ("userId") REFERENCES accounts(id); + +CREATE TABLE rooms ( + id UUID PRIMARY KEY, + "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + CREATE TABLE memories ( id UUID PRIMARY KEY, type TEXT NOT NULL, @@ -440,6 +473,9 @@ CREATE TABLE memories ( "createdAt" TIMESTAMP NOT NULL ); +ALTER TABLE ONLY memories ADD CONSTRAINT memories_roomId_fkey FOREIGN KEY ("roomId") REFERENCES rooms(id); +ALTER TABLE ONLY memories ADD CONSTRAINT memories_userId_fkey FOREIGN KEY ("userId") REFERENCES accounts(id); + CREATE INDEX memory_embedding_idx ON memories USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100); @@ -452,6 +488,11 @@ CREATE TABLE relationships ( "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); +ALTER TABLE ONLY relationships ADD CONSTRAINT friendships_id_key UNIQUE (id); +ALTER TABLE ONLY relationships ADD CONSTRAINT relationships_userA_fkey FOREIGN KEY ("userA") REFERENCES accounts(id); +ALTER TABLE ONLY relationships ADD CONSTRAINT relationships_userB_fkey FOREIGN KEY ("userB") REFERENCES accounts(id); +ALTER TABLE ONLY relationships ADD CONSTRAINT relationships_userId_fkey FOREIGN KEY ("userId") REFERENCES accounts(id); + CREATE TABLE goals ( id UUID PRIMARY KEY, "roomId" UUID NOT NULL, diff --git a/package.json b/package.json index 938819873fd..048aaef8f27 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "docker:run": "bash ./scripts/docker.sh run", "docker:bash": "bash ./scripts/docker.sh bash", "docker:start": "bash ./scripts/docker.sh start", - "docker": "pnpm docker:build && pnpm docker:run && pnpm docker:bash" + "docker": "pnpm docker:build && pnpm docker:run && pnpm docker:bash", + "test": "pnpm --dir packages/core test" }, "devDependencies": { "concurrently": "^9.1.0", @@ -27,7 +28,9 @@ "only-allow": "^1.2.1", "prettier": "^3.3.3", "typedoc": "^0.26.11", - "typescript": "5.6.3" + "typescript": "5.6.3", + "vite": "^5.4.11", + "vitest": "^2.1.5" }, "pnpm": { "overrides": { diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts index 725a65cfca8..9d53a965f3f 100644 --- a/packages/agent/src/index.ts +++ b/packages/agent/src/index.ts @@ -5,14 +5,14 @@ import { DiscordClientInterface } from "@ai16z/client-discord"; import { AutoClientInterface } from "@ai16z/client-auto"; import { TelegramClientInterface } from "@ai16z/client-telegram"; import { TwitterClientInterface } from "@ai16z/client-twitter"; -import { defaultCharacter } from "@ai16z/eliza"; -import { AgentRuntime } from "@ai16z/eliza"; -import { settings } from "@ai16z/eliza"; import { + defaultCharacter, + AgentRuntime, + settings, Character, IAgentRuntime, - IDatabaseAdapter, ModelProviderName, + elizaLogger, } from "@ai16z/eliza"; import { bootstrapPlugin } from "@ai16z/plugin-bootstrap"; import { solanaPlugin } from "@ai16z/plugin-solana"; @@ -219,7 +219,11 @@ export async function createAgent( db: any, token: string ) { - console.log("Creating runtime for character", character.name); + elizaLogger.success( + elizaLogger.successesTitle, + "Creating runtime for character", + character.name + ); return new AgentRuntime({ databaseAdapter: db, token, @@ -279,7 +283,7 @@ const startAgents = async () => { await startAgent(character, directClient); } } catch (error) { - console.error("Error starting agents:", error); + elizaLogger.error("Error starting agents:", error); } function chat() { @@ -292,12 +296,12 @@ const startAgents = async () => { }); } - console.log("Chat started. Type 'exit' to quit."); + elizaLogger.log("Chat started. Type 'exit' to quit."); chat(); }; startAgents().catch((error) => { - console.error("Unhandled error in startAgents:", error); + elizaLogger.error("Unhandled error in startAgents:", error); process.exit(1); // Exit the process after logging }); diff --git a/packages/client-direct/src/index.ts b/packages/client-direct/src/index.ts index 1ca8a97658e..123600bf555 100644 --- a/packages/client-direct/src/index.ts +++ b/packages/client-direct/src/index.ts @@ -61,7 +61,7 @@ export class DirectClient { private agents: Map; constructor() { - console.log("DirectClient constructor"); + elizaLogger.log("DirectClient constructor"); this.app = express(); this.app.use(cors()); this.agents = new Map(); diff --git a/packages/client-discord/src/actions/download_media.ts b/packages/client-discord/src/actions/download_media.ts index 0b535139cda..8c68ea44676 100644 --- a/packages/client-discord/src/actions/download_media.ts +++ b/packages/client-discord/src/actions/download_media.ts @@ -86,8 +86,8 @@ export default { callback: HandlerCallback ) => { const videoService = runtime - .getService(ServiceType.VIDEO) - .getInstance(); + .getService(ServiceType.VIDEO) + .getInstance(); if (!state) { state = (await runtime.composeState(message)) as State; } diff --git a/packages/client-discord/src/attachments.ts b/packages/client-discord/src/attachments.ts index ffe67bea150..7746beda4e3 100644 --- a/packages/client-discord/src/attachments.ts +++ b/packages/client-discord/src/attachments.ts @@ -104,8 +104,7 @@ export class AttachmentManager { } else if ( attachment.contentType?.startsWith("video/") || this.runtime - .getService(ServiceType.VIDEO) - .getInstance() + .getService(ServiceType.VIDEO) .isVideoUrl(attachment.url) ) { media = await this.processVideoAttachment(attachment); @@ -137,10 +136,16 @@ export class AttachmentManager { throw new Error("Unsupported audio/video format"); } - const transcription = await this.runtime - .getService(ServiceType.TRANSCRIPTION) - .getInstance() - .transcribeAttachment(audioBuffer); + const transcriptionService = + this.runtime.getService( + ServiceType.TRANSCRIPTION + ); + if (!transcriptionService) { + throw new Error("Transcription service not found"); + } + + const transcription = + await transcriptionService.transcribeAttachment(audioBuffer); const { title, description } = await generateSummary( this.runtime, transcription @@ -220,8 +225,7 @@ export class AttachmentManager { const response = await fetch(attachment.url); const pdfBuffer = await response.arrayBuffer(); const text = await this.runtime - .getService(ServiceType.PDF) - .getInstance() + .getService(ServiceType.PDF) .convertPdfToText(Buffer.from(pdfBuffer)); const { title, description } = await generateSummary( this.runtime, @@ -289,8 +293,9 @@ export class AttachmentManager { ): Promise { try { const { description, title } = await this.runtime - .getService(ServiceType.IMAGE_DESCRIPTION) - .getInstance() + .getService( + ServiceType.IMAGE_DESCRIPTION + ) .describeImage(attachment.url); return { id: attachment.id, @@ -322,16 +327,16 @@ export class AttachmentManager { private async processVideoAttachment( attachment: Attachment ): Promise { - if ( - this.runtime - .getService(ServiceType.VIDEO) - .getInstance() - .isVideoUrl(attachment.url) - ) { - const videoInfo = await this.runtime - .getService(ServiceType.VIDEO) - .getInstance() - .processVideo(attachment.url); + const videoService = this.runtime.getService( + ServiceType.VIDEO + ); + + if (!videoService) { + throw new Error("Video service not found"); + } + + if (videoService.isVideoUrl(attachment.url)) { + const videoInfo = await videoService.processVideo(attachment.url); return { id: attachment.id, url: attachment.url, diff --git a/packages/client-discord/src/index.ts b/packages/client-discord/src/index.ts index 992d4e9255f..0b27015b65a 100644 --- a/packages/client-discord/src/index.ts +++ b/packages/client-discord/src/index.ts @@ -25,8 +25,8 @@ import { VoiceManager } from "./voice.ts"; export class DiscordClient extends EventEmitter { apiToken: string; - private client: Client; - private runtime: IAgentRuntime; + client: Client; + runtime: IAgentRuntime; character: Character; private messageManager: MessageManager; private voiceManager: VoiceManager; @@ -193,7 +193,7 @@ export class DiscordClient extends EventEmitter { } async handleReactionRemove(reaction: MessageReaction, user: User) { - console.log("Reaction removed"); + elizaLogger.log("Reaction removed"); // if (user.bot) return; let emoji = reaction.emoji.name; diff --git a/packages/client-discord/src/messages.ts b/packages/client-discord/src/messages.ts index 4d1631664b2..25227e3e946 100644 --- a/packages/client-discord/src/messages.ts +++ b/packages/client-discord/src/messages.ts @@ -515,10 +515,23 @@ export class MessageManager { } if (message.channel.type === ChannelType.GuildVoice) { // For voice channels, use text-to-speech - const audioStream = await this.runtime - .getService(ServiceType.SPEECH_GENERATION) - .getInstance() - .generate(this.runtime, content.text); + + const speechService = + this.runtime.getService( + ServiceType.SPEECH_GENERATION + ); + + if (!speechService) { + throw new Error( + "Speech generation service not found" + ); + } + + const audioStream = await speechService.generate( + this.runtime, + content.text + ); + await this.voiceManager.playAudioStream( userId, audioStream @@ -603,10 +616,18 @@ export class MessageManager { if (message.channel.type === ChannelType.GuildVoice) { // For voice channels, use text-to-speech for the error message const errorMessage = "Sorry, I had a glitch. What was that?"; - const audioStream = await this.runtime - .getService(ServiceType.SPEECH_GENERATION) - .getInstance() - .generate(this.runtime, errorMessage); + + const speechService = this.runtime.getService( + ServiceType.SPEECH_GENERATION + ); + if (!speechService) { + throw new Error("Speech generation service not found"); + } + + const audioStream = await speechService.generate( + this.runtime, + errorMessage + ); await this.voiceManager.playAudioStream(userId, audioStream); } else { // For text channels, send the error message @@ -670,14 +691,17 @@ export class MessageManager { for (const url of urls) { if ( this.runtime - .getService(ServiceType.VIDEO) - .getInstance() + .getService(ServiceType.VIDEO) .isVideoUrl(url) ) { - const videoInfo = await this.runtime - .getService(ServiceType.VIDEO) - .getInstance() - .processVideo(url); + const videoService = this.runtime.getService( + ServiceType.VIDEO + ); + if (!videoService) { + throw new Error("Video service not found"); + } + const videoInfo = await videoService.processVideo(url); + attachments.push({ id: `youtube-${Date.now()}`, url: url, @@ -687,10 +711,16 @@ export class MessageManager { text: videoInfo.text, }); } else { - const { title, bodyContent } = await this.runtime - .getService(ServiceType.BROWSER) - .getInstance() - .getPageContent(url, this.runtime); + const browserService = this.runtime.getService( + ServiceType.BROWSER + ); + if (!browserService) { + throw new Error("Browser service not found"); + } + + const { title, bodyContent } = + await browserService.getPageContent(url, this.runtime); + const { title: newTitle, description } = await generateSummary( this.runtime, title + "\n" + bodyContent diff --git a/packages/client-discord/src/voice.ts b/packages/client-discord/src/voice.ts index e2abc0927ea..db628447562 100644 --- a/packages/client-discord/src/voice.ts +++ b/packages/client-discord/src/voice.ts @@ -20,7 +20,7 @@ import { import EventEmitter from "events"; import prism from "prism-media"; import { Readable, pipeline } from "stream"; -import { composeContext } from "@ai16z/eliza"; +import { composeContext, elizaLogger } from "@ai16z/eliza"; import { generateMessageResponse } from "@ai16z/eliza"; import { embeddingZeroVector } from "@ai16z/eliza"; import { @@ -64,6 +64,7 @@ export function getWavHeader( } import { messageCompletionFooter } from "@ai16z/eliza/src/parsing.ts"; +import { DiscordClient } from "."; const discordVoiceHandlerTemplate = `# Task: Generate conversational voice dialog for {{agentName}}. @@ -120,7 +121,7 @@ export class AudioMonitor { } }); this.readable.on("end", () => { - console.log("AudioMonitor ended"); + elizaLogger.log("AudioMonitor ended"); this.ended = true; if (this.lastFlagged < 0) return; callback(this.getBufferFromStart()); @@ -128,13 +129,13 @@ export class AudioMonitor { }); this.readable.on("speakingStopped", () => { if (this.ended) return; - console.log("Speaking stopped"); + elizaLogger.log("Speaking stopped"); if (this.lastFlagged < 0) return; callback(this.getBufferFromStart()); }); this.readable.on("speakingStarted", () => { if (this.ended) return; - console.log("Speaking started"); + elizaLogger.log("Speaking started"); this.reset(); }); } @@ -183,7 +184,7 @@ export class VoiceManager extends EventEmitter { { channel: BaseGuildVoiceChannel; monitor: AudioMonitor } > = new Map(); - constructor(client: any) { + constructor(client: DiscordClient) { super(); this.client = client.client; this.runtime = client.runtime; @@ -260,10 +261,10 @@ export class VoiceManager extends EventEmitter { member: GuildMember, channel: BaseGuildVoiceChannel ) { - const userId = member.id; - const userName = member.user.username; - const name = member.user.displayName; - const connection = getVoiceConnection(member.guild.id); + const userId = member?.id; + const userName = member?.user?.username; + const name = member?.user?.displayName; + const connection = getVoiceConnection(member?.guild?.id); const receiveStream = connection?.receiver.subscribe(userId, { autoDestroy: true, emitClose: true, @@ -368,13 +369,11 @@ export class VoiceManager extends EventEmitter { let lastChunkTime = Date.now(); let transcriptionStarted = false; let transcriptionText = ""; - console.log("new audio monitor for: ", userId); const monitor = new AudioMonitor( audioStream, 10000000, async (buffer) => { - console.log("buffer: ", buffer); const currentTime = Date.now(); const silenceDuration = currentTime - lastChunkTime; if (!buffer) { @@ -397,12 +396,20 @@ export class VoiceManager extends EventEmitter { const wavBuffer = await this.convertOpusToWav(inputBuffer); - console.log("starting transcription"); - const text = await this.runtime - .getService(ServiceType.TRANSCRIPTION) - .getInstance() - .transcribe(wavBuffer); - console.log("transcribed text: ", text); + const transcriptionService = + this.runtime.getService( + ServiceType.TRANSCRIPTION + ); + + if (!transcriptionService) { + throw new Error( + "Transcription generation service not found" + ); + } + + const text = + await transcriptionService.transcribe(wavBuffer); + transcriptionText += text; } catch (error) { console.error("Error processing audio stream:", error); @@ -539,10 +546,22 @@ export class VoiceManager extends EventEmitter { await this.runtime.updateRecentMessageState( state ); - const responseStream = await this.runtime - .getService(ServiceType.SPEECH_GENERATION) - .getInstance() - .generate(this.runtime, content.text); + + const speechService = + this.runtime.getService( + ServiceType.SPEECH_GENERATION + ); + if (!speechService) { + throw new Error( + "Speech generation service not found" + ); + } + + const responseStream = + await speechService.generate( + this.runtime, + content.text + ); if (responseStream) { await this.playAudioStream( diff --git a/packages/client-telegram/src/messageManager.ts b/packages/client-telegram/src/messageManager.ts index ca6ceb04944..3b3f53d3bf8 100644 --- a/packages/client-telegram/src/messageManager.ts +++ b/packages/client-telegram/src/messageManager.ts @@ -178,9 +178,8 @@ export class MessageManager { } if (imageUrl) { - const { title, description } = await this.imageService - .getInstance() - .describeImage(imageUrl); + const { title, description } = + await this.imageService.describeImage(imageUrl); const fullDescription = `[Image: ${title}\n${description}]`; return { description: fullDescription }; } diff --git a/packages/client-telegram/src/telegramClient.ts b/packages/client-telegram/src/telegramClient.ts index 462517c2a23..dd769c25f10 100644 --- a/packages/client-telegram/src/telegramClient.ts +++ b/packages/client-telegram/src/telegramClient.ts @@ -32,7 +32,7 @@ export class TelegramClient { this.bot.botInfo = botInfo; }); - console.log(`Bot username: @${this.bot.botInfo?.username}`); + elizaLogger.success(`Bot username: @${this.bot.botInfo?.username}`); this.messageManager.bot = this.bot; diff --git a/packages/client-twitter/src/base.ts b/packages/client-twitter/src/base.ts index 1a910c37691..96f3f29a171 100644 --- a/packages/client-twitter/src/base.ts +++ b/packages/client-twitter/src/base.ts @@ -220,7 +220,7 @@ export class ClientBase extends EventEmitter { this.runtime.getSetting("TWITTER_EMAIL"), this.runtime.getSetting("TWITTER_2FA_SECRET") ); - console.log("Logged in to Twitter"); + elizaLogger.log("Logged in to Twitter"); const cookies = await this.twitterClient.getCookies(); fs.writeFileSync( cookiesFilePath, @@ -270,7 +270,7 @@ export class ClientBase extends EventEmitter { console.error("Failed to get user ID"); return; } - console.log("Twitter user ID:", userId); + elizaLogger.log("Twitter user ID:", userId); this.twitterUserId = userId; // Initialize Twitter profile diff --git a/packages/client-twitter/src/index.ts b/packages/client-twitter/src/index.ts index 6a3097c524a..742b5ac34dc 100644 --- a/packages/client-twitter/src/index.ts +++ b/packages/client-twitter/src/index.ts @@ -1,7 +1,7 @@ import { TwitterPostClient } from "./post.ts"; import { TwitterSearchClient } from "./search.ts"; import { TwitterInteractionClient } from "./interactions.ts"; -import { IAgentRuntime, Client } from "@ai16z/eliza"; +import { IAgentRuntime, Client, elizaLogger } from "@ai16z/eliza"; class TwitterAllClient { post: TwitterPostClient; @@ -19,11 +19,11 @@ class TwitterAllClient { export const TwitterClientInterface: Client = { async start(runtime: IAgentRuntime) { - console.log("Twitter client started"); + elizaLogger.log("Twitter client started"); return new TwitterAllClient(runtime); }, async stop(runtime: IAgentRuntime) { - console.warn("Twitter client does not support stopping yet"); + elizaLogger.warn("Twitter client does not support stopping yet"); }, }; diff --git a/packages/client-twitter/src/interactions.ts b/packages/client-twitter/src/interactions.ts index 75c4f8c2c88..f0a97aa73e2 100644 --- a/packages/client-twitter/src/interactions.ts +++ b/packages/client-twitter/src/interactions.ts @@ -238,7 +238,7 @@ export class TwitterInteractionClient extends ClientBase { ); } - console.log("Thread: ", thread); + elizaLogger.debug("Thread: ", thread); const formattedConversation = thread .map( (tweet) => `@${tweet.username} (${new Date( @@ -253,7 +253,7 @@ export class TwitterInteractionClient extends ClientBase { ) .join("\n\n"); - console.log("formattedConversation: ", formattedConversation); + elizaLogger.debug("formattedConversation: ", formattedConversation); const formattedHomeTimeline = `# ${this.runtime.character.name}'s Home Timeline\n\n` + diff --git a/packages/client-twitter/src/post.ts b/packages/client-twitter/src/post.ts index d74d34ef2e1..02778dae2da 100644 --- a/packages/client-twitter/src/post.ts +++ b/packages/client-twitter/src/post.ts @@ -1,6 +1,6 @@ import { Tweet } from "agent-twitter-client"; import fs from "fs"; -import { composeContext } from "@ai16z/eliza"; +import { composeContext, elizaLogger } from "@ai16z/eliza"; import { generateText } from "@ai16z/eliza"; import { embeddingZeroVector } from "@ai16z/eliza"; import { IAgentRuntime, ModelClass } from "@ai16z/eliza"; @@ -76,7 +76,7 @@ export class TwitterPostClient extends ClientBase { generateNewTweetLoop(); // Set up next iteration }, delay); - console.log(`Next tweet scheduled in ${randomMinutes} minutes`); + elizaLogger.log(`Next tweet scheduled in ${randomMinutes} minutes`); }; if (postImmediately) { @@ -92,7 +92,7 @@ export class TwitterPostClient extends ClientBase { } private async generateNewTweet() { - console.log("Generating new tweet"); + elizaLogger.log("Generating new tweet"); try { await this.runtime.ensureUserExists( this.runtime.agentId, diff --git a/packages/client-twitter/src/search.ts b/packages/client-twitter/src/search.ts index 3ece65fa639..38d8e2d2ecb 100644 --- a/packages/client-twitter/src/search.ts +++ b/packages/client-twitter/src/search.ts @@ -234,8 +234,10 @@ export class TwitterSearchClient extends ClientBase { const imageDescriptions = []; for (const photo of selectedTweet.photos) { const description = await this.runtime - .getService(ServiceType.IMAGE_DESCRIPTION) - .getInstance() + .getService( + ServiceType.IMAGE_DESCRIPTION + ) + .getInstance() .describeImage(photo.url); imageDescriptions.push(description); } diff --git a/packages/core/.env.test b/packages/core/.env.test index 3f383adc69f..d295bdfb3b4 100644 --- a/packages/core/.env.test +++ b/packages/core/.env.test @@ -1,2 +1,6 @@ TEST_DATABASE_CLIENT=sqlite NODE_ENV=test +MAIN_WALLET_ADDRESS=TEST_MAIN_WALLET_ADDRESS_VALUE +OPENAI_API_KEY=TEST_OPENAI_API_KEY_VALUE +RPC_URL=https://api.mainnet-beta.solana.com +WALLET_PUBLIC_KEY=2weMjPLLybRMMva1fM3U31goWWrCpF59CHWNhnCJ9Vyh \ No newline at end of file diff --git a/packages/core/.gitignore b/packages/core/.gitignore index e1a400663fa..d365b5f35b6 100644 --- a/packages/core/.gitignore +++ b/packages/core/.gitignore @@ -1,4 +1,5 @@ node_modules dist elizaConfig.yaml -custom_actions/ \ No newline at end of file +custom_actions/ +cache/ \ No newline at end of file diff --git a/packages/core/jest.config.js b/packages/core/jest.config.js deleted file mode 100644 index 35d5f9f0b3c..00000000000 --- a/packages/core/jest.config.js +++ /dev/null @@ -1,25 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -export default { - preset: "ts-jest", - testEnvironment: "node", - rootDir: "./src", - testMatch: ["**/*.test.ts"], - testTimeout: 120000, - globals: { - __DEV__: true, - __TEST__: true, - __VERSION__: "0.0.1", - }, - transform: { - "^.+\\.tsx?$": [ - "ts-jest", - { - useESM: true, - }, - ], - }, - moduleNameMapper: { - "^(\\.{1,2}/.*)\\.js$": "$1", - }, - extensionsToTreatAsEsm: [".ts"], -}; diff --git a/packages/core/package.json b/packages/core/package.json index e550d9ea8da..8f637f0879d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -11,8 +11,8 @@ "watch": "tsc --watch", "dev": "tsup --format esm --dts --watch", "build:docs": "cd docs && pnpm run build", - "test": "jest --runInBand", - "test:watch": "jest --runInBand --watch" + "test": "vitest run", + "test:watch": "vitest" }, "author": "", "license": "MIT", @@ -48,7 +48,8 @@ "ts-node": "10.9.2", "tslib": "2.8.0", "tsup": "^8.3.5", - "typescript": "5.6.3" + "typescript": "5.6.3", + "@solana/web3.js": "1.95.4" }, "dependencies": { "@ai-sdk/anthropic": "^0.0.53", diff --git a/packages/core/src/embedding.ts b/packages/core/src/embedding.ts index 4c775d39465..27401c2593a 100644 --- a/packages/core/src/embedding.ts +++ b/packages/core/src/embedding.ts @@ -93,7 +93,6 @@ export async function embed(runtime: IAgentRuntime, input: string) { if ( isNode && runtime.character.modelProvider !== ModelProviderName.OPENAI && - runtime.character.modelProvider !== ModelProviderName.OLLAMA && !settings.USE_OPENAI_EMBEDDING ) { return await getLocalEmbedding(input); diff --git a/packages/core/src/generation.ts b/packages/core/src/generation.ts index 1b82fae9b9f..90e687ff83c 100644 --- a/packages/core/src/generation.ts +++ b/packages/core/src/generation.ts @@ -244,17 +244,24 @@ export async function generateText({ elizaLogger.debug( "Using local Llama model for text completion." ); - response = await runtime - .getService(ServiceType.TEXT_GENERATION) - .getInstance() - .queueTextCompletion( - context, - temperature, - _stop, - frequency_penalty, - presence_penalty, - max_response_length - ); + const textGenerationService = runtime + .getService( + ServiceType.TEXT_GENERATION + ) + .getInstance(); + + if (!textGenerationService) { + throw new Error("Text generation service not found"); + } + + response = await textGenerationService.queueTextCompletion( + context, + temperature, + _stop, + frequency_penalty, + presence_penalty, + max_response_length + ); elizaLogger.debug("Received response from local Llama model."); break; } @@ -852,16 +859,20 @@ export const generateCaption = async ( description: string; }> => { const { imageUrl } = data; - const resp = await runtime - .getService(ServiceType.IMAGE_DESCRIPTION) - .getInstance() - .describeImage(imageUrl); + const imageDescriptionService = runtime + .getService(ServiceType.IMAGE_DESCRIPTION) + .getInstance(); + + if (!imageDescriptionService) { + throw new Error("Image description service not found"); + } + + const resp = await imageDescriptionService.describeImage(imageUrl); return { title: resp.title.trim(), description: resp.description.trim(), }; }; - /** * Configuration options for generating objects with a model. */ diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts index dee40e7cfdc..6813573a897 100644 --- a/packages/core/src/runtime.ts +++ b/packages/core/src/runtime.ts @@ -42,6 +42,7 @@ import { type Memory, } from "./types.ts"; import { stringToUuid } from "./uuid.ts"; +import { v4 as uuidv4 } from 'uuid'; /** * Represents the runtime environment for an agent, handling message processing, @@ -150,17 +151,19 @@ export class AgentRuntime implements IAgentRuntime { return this.memoryManagers.get(tableName) || null; } - getService(service: ServiceType): typeof Service | null { + getService(service: ServiceType): T | null { const serviceInstance = this.services.get(service); if (!serviceInstance) { elizaLogger.error(`Service ${service} not found`); return null; } - return serviceInstance as typeof Service; + return serviceInstance as T; } - registerService(service: Service): void { - const serviceType = (service as typeof Service).serviceType; + + async registerService(service: Service): Promise { + const serviceType = service.serviceType; elizaLogger.log("Registering service:", serviceType); + if (this.services.has(serviceType)) { elizaLogger.warn( `Service ${serviceType} is already registered. Skipping registration.` @@ -168,7 +171,19 @@ export class AgentRuntime implements IAgentRuntime { return; } - this.services.set((service as typeof Service).serviceType, service); + try { + await service.initialize(this); + this.services.set(serviceType, service); + elizaLogger.success( + `Service ${serviceType} initialized successfully` + ); + } catch (error) { + elizaLogger.error( + `Failed to initialize service ${serviceType}:`, + error + ); + throw error; + } } /** @@ -213,9 +228,9 @@ export class AgentRuntime implements IAgentRuntime { this.databaseAdapter = opts.databaseAdapter; // use the character id if it exists, otherwise use the agentId if it is passed in, otherwise use the character name this.agentId = - opts.character.id ?? - opts.agentId ?? - stringToUuid(opts.character.name); + opts.character?.id ?? + opts?.agentId ?? + stringToUuid(opts.character?.name ?? uuidv4()); elizaLogger.success("Agent ID", this.agentId); @@ -269,7 +284,7 @@ export class AgentRuntime implements IAgentRuntime { this.token = opts.token; - [...(opts.character.plugins || []), ...(opts.plugins || [])].forEach( + [...(opts.character?.plugins || []), ...(opts.plugins || [])].forEach( (plugin) => { plugin.actions?.forEach((action) => { this.registerAction(action); diff --git a/packages/core/src/test_resources/constants.ts b/packages/core/src/test_resources/constants.ts new file mode 100644 index 00000000000..f60b632a03f --- /dev/null +++ b/packages/core/src/test_resources/constants.ts @@ -0,0 +1,12 @@ +import { type UUID } from "@ai16z/eliza/src/types.ts"; + +export const SERVER_URL = "http://localhost:7998"; +export const SUPABASE_URL = "https://pronvzrzfwsptkojvudd.supabase.co"; +export const SUPABASE_ANON_KEY = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InByb252enJ6ZndzcHRrb2p2dWRkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDY4NTYwNDcsImV4cCI6MjAyMjQzMjA0N30.I6_-XrqssUb2SWYg5DjsUqSodNS3_RPoET3-aPdqywM"; +export const TEST_EMAIL = "testuser123@gmail.com"; +export const TEST_PASSWORD = "testuser123@gmail.com"; +export const TEST_EMAIL_2 = "testuser234@gmail.com"; +export const TEST_PASSWORD_2 = "testuser234@gmail.com"; + +export const zeroUuid = "00000000-0000-0000-0000-000000000000" as UUID; diff --git a/packages/core/src/test_resources/createRuntime.ts b/packages/core/src/test_resources/createRuntime.ts new file mode 100644 index 00000000000..d1ad826a6ba --- /dev/null +++ b/packages/core/src/test_resources/createRuntime.ts @@ -0,0 +1,145 @@ +import { SqliteDatabaseAdapter, loadVecExtensions } from "@ai16z/adapter-sqlite"; +import { SqlJsDatabaseAdapter } from "@ai16z/adapter-sqljs"; +import { SupabaseDatabaseAdapter } from "@ai16z/adapter-supabase"; +import { DatabaseAdapter } from "../database.ts"; +import { AgentRuntime } from "../runtime.ts"; +import { + Action, + Evaluator, + ModelProviderName, + Provider, +} from "../types.ts"; +import { + SUPABASE_ANON_KEY, + SUPABASE_URL, + TEST_EMAIL, + TEST_PASSWORD, + zeroUuid, +} from "./constants.ts"; +import { User } from "./types.ts"; +import { getEndpoint } from "../models.ts"; + +export async function createRuntime({ + env, + conversationLength, + evaluators = [], + actions = [], + providers = [], +}: { + env?: Record | NodeJS.ProcessEnv; + conversationLength?: number; + evaluators?: Evaluator[]; + actions?: Action[]; + providers?: Provider[]; +}) { + let adapter: DatabaseAdapter; + let user: User; + let session: { + user: User; + }; + + switch (env?.TEST_DATABASE_CLIENT as string) { + case "sqljs": + { + const module = await import("sql.js"); + + const initSqlJs = module.default; + + // SQLite adapter + const SQL = await initSqlJs({}); + const db = new SQL.Database(); + + adapter = new SqlJsDatabaseAdapter(db); + + // Load sqlite-vss + loadVecExtensions((adapter as SqlJsDatabaseAdapter).db); + // Create a test user and session + session = { + user: { + id: zeroUuid, + email: "test@example.com", + }, + }; + } + break; + case "supabase": { + const module = await import("@supabase/supabase-js"); + + const { createClient } = module; + + const supabase = createClient( + env?.SUPABASE_URL ?? SUPABASE_URL, + env?.SUPABASE_SERVICE_API_KEY ?? SUPABASE_ANON_KEY + ); + + const { data } = await supabase.auth.signInWithPassword({ + email: TEST_EMAIL!, + password: TEST_PASSWORD!, + }); + + user = data.user as User; + session = data.session as unknown as { user: User }; + + if (!session) { + const response = await supabase.auth.signUp({ + email: TEST_EMAIL!, + password: TEST_PASSWORD!, + }); + + // Change the name of the user + const { error } = await supabase + .from("accounts") + .update({ name: "Test User" }) + .eq("id", response.data.user?.id); + + if (error) { + throw new Error( + "Create runtime error: " + JSON.stringify(error) + ); + } + + user = response.data.user as User; + session = response.data.session as unknown as { user: User }; + } + + adapter = new SupabaseDatabaseAdapter( + env?.SUPABASE_URL ?? SUPABASE_URL, + env?.SUPABASE_SERVICE_API_KEY ?? SUPABASE_ANON_KEY + ); + } + case "sqlite": + default: + { + const module = await import("better-sqlite3"); + + const Database = module.default; + + // SQLite adapter + adapter = new SqliteDatabaseAdapter(new Database(":memory:")); + + // Load sqlite-vss + await loadVecExtensions((adapter as SqliteDatabaseAdapter).db); + // Create a test user and session + session = { + user: { + id: zeroUuid, + email: "test@example.com", + }, + }; + } + break; + } + + const runtime = new AgentRuntime({ + serverUrl: getEndpoint(ModelProviderName.OPENAI), + conversationLength, + token: env!.OPENAI_API_KEY!, + modelProvider: ModelProviderName.OPENAI, + actions: actions ?? [], + evaluators: evaluators ?? [], + providers: providers ?? [], + databaseAdapter: adapter, + }); + + return { user, session, runtime }; +} diff --git a/packages/core/src/test_resources/testSetup.ts b/packages/core/src/test_resources/testSetup.ts new file mode 100644 index 00000000000..badfd3cc6a7 --- /dev/null +++ b/packages/core/src/test_resources/testSetup.ts @@ -0,0 +1,11 @@ +import dotenv from "dotenv"; +import path from "path"; + +// Load test environment variables +const envPath = path.resolve(__dirname, "../../.env.test"); +console.log('Current directory:', __dirname); +console.log('Trying to load env from:', envPath); +const result = dotenv.config({ path: envPath }); +if (result.error) { + console.error('Error loading .env.test:', result.error); +} diff --git a/packages/core/src/test_resources/types.ts b/packages/core/src/test_resources/types.ts new file mode 100644 index 00000000000..634e266cbe4 --- /dev/null +++ b/packages/core/src/test_resources/types.ts @@ -0,0 +1,6 @@ +export interface User { + id: string; + email?: string; + phone?: string; + role?: string; +} diff --git a/packages/core/src/tests/env.test.ts b/packages/core/src/tests/env.test.ts new file mode 100644 index 00000000000..369884bdf3e --- /dev/null +++ b/packages/core/src/tests/env.test.ts @@ -0,0 +1,26 @@ +import { describe, it, expect } from 'vitest'; +import fs from 'fs'; +import path from 'path'; + +describe('Environment Setup', () => { + it('should verify .env.test file exists', () => { + const possiblePaths = [ + path.join(process.cwd(), '.env.test'), + path.join(process.cwd(), 'packages/core/.env.test'), + path.join(__dirname, '../../.env.test'), + path.join(__dirname, '../.env.test'), + path.join(__dirname, '.env.test'), + ]; + + console.log('Current working directory:', process.cwd()); + console.log('__dirname:', __dirname); + + const existingPaths = possiblePaths.filter(p => { + const exists = fs.existsSync(p); + console.log(`Path ${p} exists: ${exists}`); + return exists; + }); + + expect(existingPaths.length).toBeGreaterThan(0); + }); +}); \ No newline at end of file diff --git a/packages/core/src/tests/goals.test.ts b/packages/core/src/tests/goals.test.ts index cd845d12209..26d3fee77ac 100644 --- a/packages/core/src/tests/goals.test.ts +++ b/packages/core/src/tests/goals.test.ts @@ -15,18 +15,21 @@ import { Memory, ModelProviderName, Service, + ServiceType, State, } from "../types"; +import { describe, test, expect, beforeEach, vi } from 'vitest'; + // Mock the database adapter -const mockDatabaseAdapter = { - getGoals: jest.fn(), - updateGoal: jest.fn(), - createGoal: jest.fn(), +export const mockDatabaseAdapter = { + getGoals: vi.fn(), + updateGoal: vi.fn(), + createGoal: vi.fn(), }; - +const services = new Map(); // Mock the runtime -const mockRuntime: IAgentRuntime = { +export const mockRuntime: IAgentRuntime = { databaseAdapter: mockDatabaseAdapter as any, agentId: "qweqew-qweqwe-qweqwe-qweqwe-qweeqw", serverUrl: "", @@ -87,8 +90,8 @@ const mockRuntime: IAgentRuntime = { getMemoryManager: function (_name: string): IMemoryManager | null { throw new Error("Function not implemented."); }, - registerService: function (_service: Service): void { - throw new Error("Function not implemented."); + registerService: function (service: Service): void { + services.set(service.serviceType, service); }, getSetting: function (_key: string): string | null { throw new Error("Function not implemented."); @@ -155,8 +158,10 @@ const mockRuntime: IAgentRuntime = { updateRecentMessageState: function (_state: State): Promise { throw new Error("Function not implemented."); }, - getService: function (_service: string): typeof Service | null { - throw new Error("Function not implemented."); + getService: function ( + serviceType: ServiceType + ): T | null { + return (services.get(serviceType) as T) || null; }, }; @@ -175,7 +180,7 @@ const sampleGoal: Goal = { describe("getGoals", () => { it("retrieves goals successfully", async () => { - (mockDatabaseAdapter.getGoals as jest.Mock).mockResolvedValue([ + (mockDatabaseAdapter.getGoals).mockResolvedValue([ sampleGoal, ]); @@ -194,7 +199,7 @@ describe("getGoals", () => { }); it("handles failure to retrieve goals", async () => { - (mockDatabaseAdapter.getGoals as jest.Mock).mockRejectedValue( + (mockDatabaseAdapter.getGoals).mockRejectedValue( new Error("Failed to retrieve goals") ); @@ -220,7 +225,7 @@ describe("formatGoalsAsString", () => { describe("updateGoal", () => { it("updates a goal successfully", async () => { - (mockDatabaseAdapter.updateGoal as jest.Mock).mockResolvedValue( + (mockDatabaseAdapter.updateGoal).mockResolvedValue( undefined ); @@ -231,7 +236,7 @@ describe("updateGoal", () => { }); it("handles failure to update a goal", async () => { - (mockDatabaseAdapter.updateGoal as jest.Mock).mockRejectedValue( + (mockDatabaseAdapter.updateGoal).mockRejectedValue( new Error("Failed to update goal") ); @@ -243,7 +248,7 @@ describe("updateGoal", () => { describe("createGoal", () => { it("creates a goal successfully", async () => { - (mockDatabaseAdapter.createGoal as jest.Mock).mockResolvedValue( + (mockDatabaseAdapter.createGoal).mockResolvedValue( undefined ); @@ -254,7 +259,7 @@ describe("createGoal", () => { }); it("handles failure to create a goal", async () => { - (mockDatabaseAdapter.createGoal as jest.Mock).mockRejectedValue( + (mockDatabaseAdapter.createGoal).mockRejectedValue( new Error("Failed to create goal") ); diff --git a/packages/core/src/tests/messages.test.ts b/packages/core/src/tests/messages.test.ts index 4fef72a4ff8..e82c4690bdd 100644 --- a/packages/core/src/tests/messages.test.ts +++ b/packages/core/src/tests/messages.test.ts @@ -5,6 +5,7 @@ import { formatTimestamp, } from "../messages.ts"; import { IAgentRuntime, Actor, Content, Memory, UUID } from "../types.ts"; +import { describe, test, expect, vi, beforeAll } from 'vitest'; describe("Messages Library", () => { let runtime: IAgentRuntime; @@ -15,9 +16,9 @@ describe("Messages Library", () => { // Mock runtime with necessary methods runtime = { databaseAdapter: { - // Casting to a Jest mock function - getParticipantsForRoom: jest.fn(), - getAccountById: jest.fn(), + // Using vi.fn() instead of jest.fn() + getParticipantsForRoom: vi.fn(), + getAccountById: vi.fn(), }, } as unknown as IAgentRuntime; @@ -38,29 +39,22 @@ describe("Messages Library", () => { }); test("getActorDetails should return actors based on roomId", async () => { - // Mocking the database adapter methods const roomId: UUID = "room1234-1234-1234-1234-123456789abc" as UUID; - // Properly mocking the resolved values of the mocked methods - ( - runtime.databaseAdapter.getParticipantsForRoom as jest.Mock - ).mockResolvedValue([userId]); - (runtime.databaseAdapter.getAccountById as jest.Mock).mockResolvedValue( - { - id: userId, - name: "Test User", - username: "testuser", - details: { - tagline: "A test user", - summary: "This is a test user for the system.", - }, - } - ); + // Using vi.mocked() type assertion instead of jest.Mock casting + vi.mocked(runtime.databaseAdapter.getParticipantsForRoom).mockResolvedValue([userId]); + vi.mocked(runtime.databaseAdapter.getAccountById).mockResolvedValue({ + id: userId, + name: "Test User", + username: "testuser", + details: { + tagline: "A test user", + summary: "This is a test user for the system.", + }, + }); - // Calling the function under test const result = await getActorDetails({ runtime, roomId }); - // Assertions expect(result.length).toBeGreaterThan(0); expect(result[0].name).toBe("Test User"); expect(result[0].details?.tagline).toBe("A test user"); @@ -69,7 +63,6 @@ describe("Messages Library", () => { test("formatActors should format actors into a readable string", () => { const formattedActors = formatActors({ actors }); - // Assertions expect(formattedActors).toContain("Test User"); expect(formattedActors).toContain("A test user"); expect(formattedActors).toContain( diff --git a/packages/core/src/tests/models.test.ts b/packages/core/src/tests/models.test.ts index fd602abe060..6cc554cbe37 100644 --- a/packages/core/src/tests/models.test.ts +++ b/packages/core/src/tests/models.test.ts @@ -1,9 +1,19 @@ import { getModel, getEndpoint } from "../models.ts"; import { ModelProviderName, ModelClass } from "../types.ts"; +import { describe, test, expect, vi } from 'vitest'; -jest.mock("../settings", () => ({ - loadEnv: jest.fn(), // Mock the loadEnv function -})); +vi.mock("../settings", () => { + return { + default: { + SMALL_OPENROUTER_MODEL: "mock-small-model", + OPENROUTER_MODEL: "mock-default-model", + OPENAI_API_KEY: "mock-openai-key", + ANTHROPIC_API_KEY: "mock-anthropic-key", + OPENROUTER_API_KEY: "mock-openrouter-key", + }, + loadEnv: vi.fn(), + } +}); describe("Model Provider Tests", () => { test("should retrieve the correct model for OpenAI SMALL", () => { @@ -21,6 +31,11 @@ describe("Model Provider Tests", () => { expect(model).toBe("llama-3.2-90b-text-preview"); }); + test("should retrieve the correct model for OpenRouter SMALL", () => { + const model = getModel(ModelProviderName.OPENROUTER, ModelClass.SMALL); + expect(model).toBe("mock-small-model"); + }); + test("should retrieve the correct endpoint for OpenAI", () => { const endpoint = getEndpoint(ModelProviderName.OPENAI); expect(endpoint).toBe("https://api.openai.com/v1"); @@ -31,7 +46,7 @@ describe("Model Provider Tests", () => { expect(endpoint).toBe("https://api.anthropic.com/v1"); }); - test("should handle invalid model provider", () => { + test("should handle invalid model provider for getModel", () => { expect(() => getModel("INVALID_PROVIDER" as any, ModelClass.SMALL) ).toThrow(); diff --git a/packages/core/src/tests/relationships.test.ts b/packages/core/src/tests/relationships.test.ts index cb42f0ac1c3..5b52dc0c9ba 100644 --- a/packages/core/src/tests/relationships.test.ts +++ b/packages/core/src/tests/relationships.test.ts @@ -5,12 +5,13 @@ import { formatRelationships, } from "../relationships"; import { IAgentRuntime, type Relationship, type UUID } from "../types"; +import { describe, expect, vi } from 'vitest'; // Mock runtime and databaseAdapter const mockDatabaseAdapter = { - createRelationship: jest.fn(), - getRelationship: jest.fn(), - getRelationships: jest.fn(), + createRelationship: vi.fn(), + getRelationship: vi.fn(), + getRelationships: vi.fn(), }; const mockRuntime: IAgentRuntime = { databaseAdapter: mockDatabaseAdapter, @@ -26,7 +27,7 @@ describe("Relationships Module", () => { const mockUserId: UUID = generateRandomUUID(); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe("createRelationship", () => { diff --git a/packages/core/src/tests/token.test.ts b/packages/core/src/tests/token.test.ts new file mode 100644 index 00000000000..b464043f2fa --- /dev/null +++ b/packages/core/src/tests/token.test.ts @@ -0,0 +1,76 @@ +// Now import other modules +import { createRuntime } from "../test_resources/createRuntime"; +import { TokenProvider, WalletProvider } from "@ai16z/plugin-solana"; +import { Connection, PublicKey } from "@solana/web3.js"; +import { describe, test, expect, beforeEach, vi } from 'vitest'; +import NodeCache from 'node-cache'; + +describe("TokenProvider Tests", async () => { + let tokenProvider: TokenProvider; + + beforeEach(async () => { + // Clear all mocks before each test + vi.clearAllMocks(); + + const { runtime } = await createRuntime({ + env: process.env, + conversationLength: 10, + }); + + const walletProvider = new WalletProvider( + new Connection(runtime.getSetting("RPC_URL")), + new PublicKey(runtime.getSetting("WALLET_PUBLIC_KEY")) + ); + // Create new instance of TokenProvider + tokenProvider = new TokenProvider( + "2weMjPLLybRMMva1fM3U31goWWrCpF59CHWNhnCJ9Vyh", + walletProvider + ); + + // Clear the cache and ensure it's empty + (tokenProvider as any).cache.flushAll(); + (tokenProvider as any).cache.close(); + (tokenProvider as any).cache = new NodeCache(); + + // Mock the getCachedData method instead + vi.spyOn(tokenProvider as any, 'getCachedData').mockReturnValue(null); + }); + + test("should fetch token security data", async () => { + + // Mock the response for the fetchTokenSecurity call + const mockFetchResponse = { + success: true, + data: { + ownerBalance: "100", + creatorBalance: "50", + ownerPercentage: 10, + creatorPercentage: 5, + top10HolderBalance: "200", + top10HolderPercent: 20, + }, + }; + + // Mock fetchWithRetry function + const fetchSpy = vi + .spyOn(tokenProvider as any, "fetchWithRetry") + .mockResolvedValue(mockFetchResponse); + + // Run the fetchTokenSecurity method + const securityData = await tokenProvider.fetchTokenSecurity(); + // Check if the data returned is correct + expect(securityData).toEqual({ + ownerBalance: "100", + creatorBalance: "50", + ownerPercentage: 10, + creatorPercentage: 5, + top10HolderBalance: "200", + top10HolderPercent: 20, + }); + + // Ensure the mock was called with correct URL + expect(fetchSpy).toHaveBeenCalledWith( + expect.stringContaining("https://public-api.birdeye.so/defi/token_security?address=2weMjPLLybRMMva1fM3U31goWWrCpF59CHWNhnCJ9Vyh"), + ); + }); +}); diff --git a/packages/core/src/tests/videoGeneration.test.ts b/packages/core/src/tests/videoGeneration.test.ts new file mode 100644 index 00000000000..e331e8168ee --- /dev/null +++ b/packages/core/src/tests/videoGeneration.test.ts @@ -0,0 +1,126 @@ +import { IAgentRuntime, Memory, State } from "@ai16z/eliza"; +import { videoGenerationPlugin } from "../index"; +import { describe, it, expect, beforeEach, vi } from 'vitest'; + +// Mock the fetch function +global.fetch = vi.fn(); + +// Mock the fs module +vi.mock('fs', () => ({ + writeFileSync: vi.fn(), + existsSync: vi.fn(), + mkdirSync: vi.fn(), +})); + +describe('Video Generation Plugin', () => { + let mockRuntime: IAgentRuntime; + let mockCallback: ReturnType; + + beforeEach(() => { + // Reset mocks + vi.clearAllMocks(); + + // Setup mock runtime + mockRuntime = { + getSetting: vi.fn().mockReturnValue('mock-api-key'), + agentId: 'mock-agent-id', + composeState: vi.fn().mockResolvedValue({}), + } as unknown as IAgentRuntime; + + mockCallback = vi.fn(); + + // Setup fetch mock for successful response + (global.fetch as ReturnType).mockImplementation(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve({ + id: 'mock-generation-id', + status: 'completed', + assets: { + video: 'https://example.com/video.mp4' + } + }), + text: () => Promise.resolve(''), + }) + ); + }); + + it('should validate when API key is present', async () => { + const mockMessage = {} as Memory; + const result = await videoGenerationPlugin.actions[0].validate(mockRuntime, mockMessage); + expect(result).toBe(true); + expect(mockRuntime.getSetting).toHaveBeenCalledWith('LUMA_API_KEY'); + }); + + it('should handle video generation request', async () => { + const mockMessage = { + content: { + text: 'Generate a video of a sunset' + } + } as Memory; + const mockState = {} as State; + + await videoGenerationPlugin.actions[0].handler( + mockRuntime, + mockMessage, + mockState, + {}, + mockCallback + ); + + // Check initial callback + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + text: expect.stringContaining('I\'ll generate a video based on your prompt') + }) + ); + + // Check final callback with video + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + text: 'Here\'s your generated video!', + attachments: expect.arrayContaining([ + expect.objectContaining({ + source: 'videoGeneration' + }) + ]) + }), + expect.arrayContaining([expect.stringMatching(/generated_video_.*\.mp4/)]) + ); + }); + + it('should handle API errors gracefully', async () => { + // Mock API error + (global.fetch as ReturnType).mockImplementationOnce(() => + Promise.resolve({ + ok: false, + status: 500, + statusText: 'Internal Server Error', + text: () => Promise.resolve('API Error'), + }) + ); + + const mockMessage = { + content: { + text: 'Generate a video of a sunset' + } + } as Memory; + const mockState = {} as State; + + await videoGenerationPlugin.actions[0].handler( + mockRuntime, + mockMessage, + mockState, + {}, + mockCallback + ); + + // Check error callback + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + text: expect.stringContaining('Video generation failed'), + error: true + }) + ); + }); +}); \ No newline at end of file diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index b4fefaa1145..1c89a76c283 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -524,15 +524,24 @@ export interface IMemoryManager { export abstract class Service { private static instance: Service | null = null; - static serviceType: ServiceType; + + static get serviceType(): ServiceType { + throw new Error("Service must implement static serviceType getter"); + } public static getInstance(): T { if (!Service.instance) { - // Use this.prototype.constructor to instantiate the concrete class Service.instance = new (this as any)(); } return Service.instance as T; } + + get serviceType(): ServiceType { + return (this.constructor as typeof Service).serviceType; + } + + // Add abstract initialize method that must be implemented by derived classes + abstract initialize(runtime: IAgentRuntime): Promise; } export interface IAgentRuntime { @@ -556,7 +565,7 @@ export interface IAgentRuntime { getMemoryManager(name: string): IMemoryManager | null; - getService(service: string): typeof Service | null; + getService(service: ServiceType): T | null; registerService(service: Service): void; @@ -601,13 +610,13 @@ export interface IAgentRuntime { export interface IImageDescriptionService extends Service { getInstance(): IImageDescriptionService; - initialize(modelId?: string | null, device?: string | null): Promise; describeImage( imageUrl: string ): Promise<{ title: string; description: string }>; } export interface ITranscriptionService extends Service { + getInstance(): ITranscriptionService; transcribeAttachment(audioBuffer: ArrayBuffer): Promise; transcribeAttachmentLocally( audioBuffer: ArrayBuffer @@ -617,6 +626,7 @@ export interface ITranscriptionService extends Service { } export interface IVideoService extends Service { + getInstance(): IVideoService; isVideoUrl(url: string): boolean; processVideo(url: string): Promise; fetchVideoInfo(url: string): Promise; @@ -646,7 +656,7 @@ export interface ITextGenerationService extends Service { } export interface IBrowserService extends Service { - initialize(): Promise; + getInstance(): IBrowserService; closeBrowser(): Promise; getPageContent( url: string, @@ -655,10 +665,12 @@ export interface IBrowserService extends Service { } export interface ISpeechService extends Service { + getInstance(): ISpeechService; generate(runtime: IAgentRuntime, text: string): Promise; } export interface IPdfService extends Service { + getInstance(): IPdfService; convertPdfToText(pdfBuffer: Buffer): Promise; } diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts new file mode 100644 index 00000000000..2ce1d5d7f32 --- /dev/null +++ b/packages/core/vitest.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'vitest/config'; +import path from 'path'; + +export default defineConfig({ + test: { + setupFiles: ['./src/test_resources/testSetup.ts'], + environment: 'node', + globals: true, + testTimeout: 120000, + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, +}); \ No newline at end of file diff --git a/packages/plugin-node/src/index.ts b/packages/plugin-node/src/index.ts index 678db6460e2..6969a05592d 100644 --- a/packages/plugin-node/src/index.ts +++ b/packages/plugin-node/src/index.ts @@ -1,31 +1,28 @@ -export * from "./services/browser.ts"; -export * from "./services/image.ts"; -export * from "./services/llama.ts"; -export * from "./services/pdf.ts"; -export * from "./services/speech.ts"; -export * from "./services/transcription.ts"; -export * from "./services/video.ts"; +export * from "./services/index.ts"; import { Plugin } from "@ai16z/eliza"; -import { BrowserService } from "./services/browser.ts"; -import { ImageDescriptionService } from "./services/image.ts"; -import { LlamaService } from "./services/llama.ts"; -import { PdfService } from "./services/pdf.ts"; -import { SpeechService } from "./services/speech.ts"; -import { TranscriptionService } from "./services/transcription.ts"; -import { VideoService } from "./services/video.ts"; + +import { + BrowserService, + ImageDescriptionService, + LlamaService, + PdfService, + SpeechService, + TranscriptionService, + VideoService, +} from "./services/index.ts"; export const nodePlugin: Plugin = { name: "default", description: "Default plugin, with basic actions and evaluators", services: [ - BrowserService, - ImageDescriptionService, - LlamaService, - PdfService, - SpeechService, - TranscriptionService, - VideoService, + new BrowserService(), + new ImageDescriptionService(), + new LlamaService(), + new PdfService(), + new SpeechService(), + new TranscriptionService(), + new VideoService(), ], }; diff --git a/packages/plugin-node/src/services/image.ts b/packages/plugin-node/src/services/image.ts index e4430c651ac..ac4333abe32 100644 --- a/packages/plugin-node/src/services/image.ts +++ b/packages/plugin-node/src/services/image.ts @@ -1,5 +1,4 @@ -// Current image recognition service -- local recognition working, no openai recognition -import { models } from "@ai16z/eliza"; +import { elizaLogger, models } from "@ai16z/eliza"; import { Service } from "@ai16z/eliza"; import { IAgentRuntime, ModelProviderName, ServiceType } from "@ai16z/eliza"; import { @@ -19,64 +18,24 @@ import os from "os"; import path from "path"; export class ImageDescriptionService extends Service { + static serviceType: ServiceType = ServiceType.IMAGE_DESCRIPTION; + private modelId: string = "onnx-community/Florence-2-base-ft"; private device: string = "gpu"; private model: PreTrainedModel | null = null; private processor: Florence2Processor | null = null; private tokenizer: PreTrainedTokenizer | null = null; private initialized: boolean = false; - - static serviceType: ServiceType = ServiceType.IMAGE_DESCRIPTION; - + private runtime: IAgentRuntime | null = null; private queue: string[] = []; private processing: boolean = false; - constructor() { - super(); - } - - async initialize( - device: string | null = null, - runtime: IAgentRuntime - ): Promise { - if (this.initialized) { - return; - } - + async initialize(runtime: IAgentRuntime): Promise { + this.runtime = runtime; const model = models[runtime?.character?.modelProvider]; if (model === models[ModelProviderName.LLAMALOCAL]) { - this.modelId = "onnx-community/Florence-2-base-ft"; - - env.allowLocalModels = false; - env.allowRemoteModels = true; - env.backends.onnx.logLevel = "fatal"; - env.backends.onnx.wasm.proxy = false; - env.backends.onnx.wasm.numThreads = 1; - - console.log("Downloading model..."); - - this.model = - await Florence2ForConditionalGeneration.from_pretrained( - this.modelId, - { - device: "gpu", - progress_callback: (progress) => { - if (progress.status === "downloading") { - console.log( - `Model download progress: ${JSON.stringify(progress)}` - ); - } - }, - } - ); - - console.log("Model downloaded successfully."); - - this.processor = (await AutoProcessor.from_pretrained( - this.modelId - )) as Florence2Processor; - this.tokenizer = await AutoTokenizer.from_pretrained(this.modelId); + await this.initializeLocalModel(); } else { this.modelId = "gpt-4o-mini"; this.device = "cloud"; @@ -85,43 +44,77 @@ export class ImageDescriptionService extends Service { this.initialized = true; } + private async initializeLocalModel(): Promise { + env.allowLocalModels = false; + env.allowRemoteModels = true; + env.backends.onnx.logLevel = "fatal"; + env.backends.onnx.wasm.proxy = false; + env.backends.onnx.wasm.numThreads = 1; + + elizaLogger.log("Downloading Florence model..."); + + this.model = await Florence2ForConditionalGeneration.from_pretrained( + this.modelId, + { + device: "gpu", + progress_callback: (progress) => { + if (progress.status === "downloading") { + elizaLogger.log( + `Model download progress: ${JSON.stringify(progress)}` + ); + } + }, + } + ); + + elizaLogger.success("Florence model downloaded successfully"); + + this.processor = (await AutoProcessor.from_pretrained( + this.modelId + )) as Florence2Processor; + this.tokenizer = await AutoTokenizer.from_pretrained(this.modelId); + } + async describeImage( - imageUrl: string, - device?: string, - runtime?: IAgentRuntime + imageUrl: string ): Promise<{ title: string; description: string }> { - this.initialize(device, runtime); + if (!this.initialized) { + throw new Error("ImageDescriptionService not initialized"); + } if (this.device === "cloud") { - return this.recognizeWithOpenAI(imageUrl, runtime); - } else { - this.queue.push(imageUrl); - this.processQueue(); - - return new Promise((resolve, reject) => { - const checkQueue = () => { - const index = this.queue.indexOf(imageUrl); - if (index !== -1) { - setTimeout(checkQueue, 100); - } else { - resolve(this.processImage(imageUrl)); - } - }; - checkQueue(); - }); + if (!this.runtime) { + throw new Error( + "Runtime is required for OpenAI image recognition" + ); + } + return this.recognizeWithOpenAI(imageUrl); } + + this.queue.push(imageUrl); + this.processQueue(); + + return new Promise((resolve, reject) => { + const checkQueue = () => { + const index = this.queue.indexOf(imageUrl); + if (index !== -1) { + setTimeout(checkQueue, 100); + } else { + resolve(this.processImage(imageUrl)); + } + }; + checkQueue(); + }); } private async recognizeWithOpenAI( - imageUrl: string, - runtime + imageUrl: string ): Promise<{ title: string; description: string }> { const isGif = imageUrl.toLowerCase().endsWith(".gif"); let imageData: Buffer | null = null; try { if (isGif) { - console.log("Processing GIF: extracting first frame"); const { filePath } = await this.extractFirstFrameFromGif(imageUrl); imageData = fs.readFileSync(filePath); @@ -141,19 +134,20 @@ export class ImageDescriptionService extends Service { const prompt = "Describe this image and give it a title. The first line should be the title, and then a line break, then a detailed description of the image. Respond with the format 'title\ndescription'"; - const text = await this.requestOpenAI( imageUrl, imageData, prompt, - isGif, - runtime + isGif ); - const title = text.split("\n")[0]; - const description = text.split("\n").slice(1).join("\n"); - return { title, description }; + + const [title, ...descriptionParts] = text.split("\n"); + return { + title, + description: descriptionParts.join("\n"), + }; } catch (error) { - console.error("Error in recognizeWithOpenAI:", error); + elizaLogger.error("Error in recognizeWithOpenAI:", error); throw error; } } @@ -162,50 +156,21 @@ export class ImageDescriptionService extends Service { imageUrl: string, imageData: Buffer, prompt: string, - isGif: boolean, - runtime: IAgentRuntime + isGif: boolean ): Promise { - for (let retryAttempts = 0; retryAttempts < 3; retryAttempts++) { + for (let attempt = 0; attempt < 3; attempt++) { try { - let body; - if (isGif) { - const base64Image = imageData.toString("base64"); - body = JSON.stringify({ - model: "gpt-4o-mini", - messages: [ - { - role: "user", - content: [ - { type: "text", text: prompt }, - { - type: "image_url", - image_url: { - url: `data:image/png;base64,${base64Image}`, - }, - }, - ], - }, - ], - max_tokens: 500, - }); - } else { - body = JSON.stringify({ - model: "gpt-4o-mini", - messages: [ - { - role: "user", - content: [ - { type: "text", text: prompt }, - { - type: "image_url", - image_url: { url: imageUrl }, - }, - ], - }, - ], - max_tokens: 300, - }); - } + const content = [ + { type: "text", text: prompt }, + { + type: "image_url", + image_url: { + url: isGif + ? `data:image/png;base64,${imageData.toString("base64")}` + : imageUrl, + }, + }, + ]; const response = await fetch( "https://api.openai.com/v1/chat/completions", @@ -213,9 +178,13 @@ export class ImageDescriptionService extends Service { method: "POST", headers: { "Content-Type": "application/json", - Authorization: `Bearer ${runtime.getSetting("OPENAI_API_KEY")}`, + Authorization: `Bearer ${this.runtime.getSetting("OPENAI_API_KEY")}`, }, - body: body, + body: JSON.stringify({ + model: "gpt-4o-mini", + messages: [{ role: "user", content }], + max_tokens: isGif ? 500 : 300, + }), } ); @@ -226,13 +195,11 @@ export class ImageDescriptionService extends Service { const data = await response.json(); return data.choices[0].message.content; } catch (error) { - console.log( - `Error during OpenAI request (attempt ${retryAttempts + 1}):`, + elizaLogger.error( + `OpenAI request failed (attempt ${attempt + 1}):`, error ); - if (retryAttempts === 2) { - throw error; - } + if (attempt === 2) throw error; } } throw new Error( @@ -241,30 +208,30 @@ export class ImageDescriptionService extends Service { } private async processQueue(): Promise { - if (this.processing || this.queue.length === 0) { - return; - } + if (this.processing || this.queue.length === 0) return; this.processing = true; - while (this.queue.length > 0) { const imageUrl = this.queue.shift(); await this.processImage(imageUrl); } - this.processing = false; } private async processImage( imageUrl: string ): Promise<{ title: string; description: string }> { - console.log("***** PROCESSING IMAGE", imageUrl); + if (!this.model || !this.processor || !this.tokenizer) { + throw new Error("Model components not initialized"); + } + + elizaLogger.log("Processing image:", imageUrl); const isGif = imageUrl.toLowerCase().endsWith(".gif"); let imageToProcess = imageUrl; try { if (isGif) { - console.log("Processing GIF: extracting first frame"); + elizaLogger.log("Extracting first frame from GIF"); const { filePath } = await this.extractFirstFrameFromGif(imageUrl); imageToProcess = filePath; @@ -272,44 +239,32 @@ export class ImageDescriptionService extends Service { const image = await RawImage.fromURL(imageToProcess); const visionInputs = await this.processor(image); - const prompts = this.processor.construct_prompts(""); const textInputs = this.tokenizer(prompts); - console.log("***** GENERATING"); - + elizaLogger.log("Generating image description"); const generatedIds = (await this.model.generate({ ...textInputs, ...visionInputs, max_new_tokens: 256, })) as Tensor; - console.log("***** GENERATED IDS", generatedIds); - const generatedText = this.tokenizer.batch_decode(generatedIds, { skip_special_tokens: false, })[0]; - console.log("***** GENERATED TEXT"); - console.log(generatedText); - const result = this.processor.post_process_generation( generatedText, "", image.size ); - console.log("***** RESULT"); - console.log(result); - const detailedCaption = result[""] as string; - - // TODO: handle this better - return { title: detailedCaption, description: detailedCaption }; } catch (error) { - console.error("Error in processImage:", error); + elizaLogger.error("Error processing image:", error); + throw error; } finally { if (isGif && imageToProcess !== imageUrl) { fs.unlinkSync(imageToProcess); @@ -325,19 +280,16 @@ export class ImageDescriptionService extends Service { frames: 1, outputType: "png", }); - const firstFrame = frameData[0]; - const tempDir = os.tmpdir(); - const tempFilePath = path.join(tempDir, `gif_frame_${Date.now()}.png`); + const tempFilePath = path.join( + os.tmpdir(), + `gif_frame_${Date.now()}.png` + ); return new Promise((resolve, reject) => { const writeStream = fs.createWriteStream(tempFilePath); - firstFrame.getImage().pipe(writeStream); - - writeStream.on("finish", () => { - resolve({ filePath: tempFilePath }); - }); - + frameData[0].getImage().pipe(writeStream); + writeStream.on("finish", () => resolve({ filePath: tempFilePath })); writeStream.on("error", reject); }); } diff --git a/packages/plugin-node/src/services/index.ts b/packages/plugin-node/src/services/index.ts new file mode 100644 index 00000000000..95ed3e04ae9 --- /dev/null +++ b/packages/plugin-node/src/services/index.ts @@ -0,0 +1,17 @@ +import { BrowserService } from "./browser.ts"; +import { ImageDescriptionService } from "./image.ts"; +import { LlamaService } from "./llama.ts"; +import { PdfService } from "./pdf.ts"; +import { SpeechService } from "./speech.ts"; +import { TranscriptionService } from "./transcription.ts"; +import { VideoService } from "./video.ts"; + +export { + BrowserService, + ImageDescriptionService, + LlamaService, + PdfService, + SpeechService, + TranscriptionService, + VideoService, +}; diff --git a/packages/plugin-node/src/services/llama.ts b/packages/plugin-node/src/services/llama.ts index f8bc8be4147..720972278f3 100644 --- a/packages/plugin-node/src/services/llama.ts +++ b/packages/plugin-node/src/services/llama.ts @@ -1,4 +1,4 @@ -import { elizaLogger, ServiceType } from "@ai16z/eliza"; +import { elizaLogger, IAgentRuntime, ServiceType } from "@ai16z/eliza"; import { Service } from "@ai16z/eliza"; import fs from "fs"; import https from "https"; @@ -180,6 +180,9 @@ export class LlamaService extends Service { const modelName = "model.gguf"; this.modelPath = path.join(__dirname, modelName); } + + async initialize(runtime: IAgentRuntime): Promise {} + private async ensureInitialized() { if (!this.modelInitialized) { await this.initializeModel(); diff --git a/packages/plugin-node/src/services/pdf.ts b/packages/plugin-node/src/services/pdf.ts index ad899672fc1..b1138814486 100644 --- a/packages/plugin-node/src/services/pdf.ts +++ b/packages/plugin-node/src/services/pdf.ts @@ -1,4 +1,4 @@ -import { Service, ServiceType } from "@ai16z/eliza"; +import { IAgentRuntime, Service, ServiceType } from "@ai16z/eliza"; import { getDocument, PDFDocumentProxy } from "pdfjs-dist"; import { TextItem, TextMarkedContent } from "pdfjs-dist/types/src/display/api"; @@ -9,6 +9,8 @@ export class PdfService extends Service { super(); } + async initialize(runtime: IAgentRuntime): Promise {} + async convertPdfToText(pdfBuffer: Buffer): Promise { // Convert Buffer to Uint8Array const uint8Array = new Uint8Array(pdfBuffer); diff --git a/packages/plugin-node/src/services/speech.ts b/packages/plugin-node/src/services/speech.ts index 66e45a81edf..e5a0beed45e 100644 --- a/packages/plugin-node/src/services/speech.ts +++ b/packages/plugin-node/src/services/speech.ts @@ -3,6 +3,7 @@ import { IAgentRuntime, ISpeechService, ServiceType } from "@ai16z/eliza"; import { getWavHeader } from "./audioUtils.ts"; import { synthesize } from "../vendor/vits.ts"; import { Service } from "@ai16z/eliza"; + function prependWavHeader( readable: Readable, audioLength: number, @@ -107,8 +108,11 @@ async function textToSpeech(runtime: IAgentRuntime, text: string) { } } -export class SpeechService extends Service implements ISpeechService { +export class SpeechService extends Service { static serviceType: ServiceType = ServiceType.SPEECH_GENERATION; + + async initialize(runtime: IAgentRuntime): Promise {} + async generate(runtime: IAgentRuntime, text: string): Promise { // check for elevenlabs API key if (runtime.getSetting("ELEVENLABS_XI_API_KEY")) { diff --git a/packages/plugin-node/src/services/transcription.ts b/packages/plugin-node/src/services/transcription.ts index 0360dcec918..dd2da549494 100644 --- a/packages/plugin-node/src/services/transcription.ts +++ b/packages/plugin-node/src/services/transcription.ts @@ -1,4 +1,4 @@ -import { settings } from "@ai16z/eliza"; +import { IAgentRuntime, settings } from "@ai16z/eliza"; import { Service, ServiceType } from "@ai16z/eliza"; import { exec } from "child_process"; import { File } from "formdata-node"; @@ -27,6 +27,8 @@ export class TranscriptionService extends Service { private queue: { audioBuffer: ArrayBuffer; resolve: Function }[] = []; private processing: boolean = false; + async initialize(runtime: IAgentRuntime): Promise {} + constructor() { super(); const rootDir = path.resolve(__dirname, "../../"); diff --git a/packages/plugin-node/src/services/video.ts b/packages/plugin-node/src/services/video.ts index a8bee25ac7e..bbc16b09735 100644 --- a/packages/plugin-node/src/services/video.ts +++ b/packages/plugin-node/src/services/video.ts @@ -22,6 +22,8 @@ export class VideoService extends Service { this.ensureCacheDirectoryExists(); } + async initialize(runtime: IAgentRuntime): Promise {} + private ensureCacheDirectoryExists() { if (!fs.existsSync(this.CONTENT_CACHE_DIR)) { fs.mkdirSync(this.CONTENT_CACHE_DIR); @@ -327,10 +329,15 @@ export class VideoService extends Service { console.log("Starting transcription..."); const startTime = Date.now(); - const transcript = await runtime - .getService(ServiceType.TRANSCRIPTION) - .getInstance() - .transcribe(audioBuffer); + const transcriptionService = runtime + .getService(ServiceType.TRANSCRIPTION) + .getInstance(); + if (!transcriptionService) { + throw new Error("Transcription service not found"); + } + + const transcript = await transcriptionService.transcribe(audioBuffer); + const endTime = Date.now(); console.log( `Transcription completed in ${(endTime - startTime) / 1000} seconds` diff --git a/packages/plugin-solana/src/index.ts b/packages/plugin-solana/src/index.ts index 78d992be442..6fda33841df 100644 --- a/packages/plugin-solana/src/index.ts +++ b/packages/plugin-solana/src/index.ts @@ -12,6 +12,10 @@ import { Plugin } from "@ai16z/eliza"; import { walletProvider } from "./providers/wallet.ts"; import { trustScoreProvider } from "./providers/trustScoreProvider.ts"; import { trustEvaluator } from "./evaluators/trust.ts"; +import { TokenProvider } from "./providers/token.ts"; +import { WalletProvider } from "./providers/wallet.ts"; + +export { TokenProvider, WalletProvider }; export const solanaPlugin: Plugin = { name: "solana", diff --git a/packages/plugin-starknet/src/actions/unruggable.ts b/packages/plugin-starknet/src/actions/unruggable.ts index d558c27587d..7318ceb8e41 100644 --- a/packages/plugin-starknet/src/actions/unruggable.ts +++ b/packages/plugin-starknet/src/actions/unruggable.ts @@ -1,5 +1,3 @@ -// TODO: add unruggable - import { ActionExample, elizaLogger, @@ -12,13 +10,13 @@ import { } from "@ai16z/eliza"; import { composeContext } from "@ai16z/eliza"; import { generateObject } from "@ai16z/eliza"; -import { - executeSwap as executeAvnuSwap, - fetchQuotes, - QuoteRequest, -} from "@avnu/avnu-sdk"; -import { getStarknetAccount, validateSettings } from "../utils/index.ts"; +import { + getStarknetAccount, + getStarknetProvider, + validateSettings, +} from "../utils/index.ts"; +import { DeployData, Factory } from "@unruggable_starknet/core"; interface SwapContent { sellTokenAddress: string; @@ -26,66 +24,64 @@ interface SwapContent { sellAmount: string; } -export function isSwapContent(content: SwapContent): content is SwapContent { +export function isDeployTokenContent( + content: DeployData +): content is DeployData { // Validate types const validTypes = - typeof content.sellTokenAddress === "string" && - typeof content.buyTokenAddress === "string" && - typeof content.sellAmount === "string"; + typeof content.name === "string" && + typeof content.symbol === "string" && + typeof content.owner === "string" && + typeof content.initialSupply === "string"; if (!validTypes) { return false; } // Validate addresses (must be 32-bytes long with 0x prefix) const validAddresses = - content.sellTokenAddress.startsWith("0x") && - content.sellTokenAddress.length === 66 && - content.buyTokenAddress.startsWith("0x") && - content.buyTokenAddress.length === 66; + content.name.length > 2 && + content.symbol.length > 2 && + parseInt(content.initialSupply) > 0 && + content.owner.startsWith("0x") && + content.owner.length === 66; return validAddresses; } -const swapTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. - -These are known addresses you will get asked to swap, use these addresses for sellTokenAddress and buyTokenAddress: -- BROTHER/brother/$brother: 0x03b405a98c9e795d427fe82cdeeeed803f221b52471e3a757574a2b4180793ee -- BTC/btc: 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac -- ETH/eth: 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 -- STRK/strk: 0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d -- LORDS/lords: 0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49 +const deployTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. Example response: \`\`\`json { - "sellTokenAddress": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", - "buyTokenAddress": "0x124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49", - "sellAmount": "1000000000000000000" + "name": "Brother", + "symbol": "BROTHER", + "owner": "0x0000000000000000000000000000000000000000000000000000000000000000", + "initialSupply": "1000000000000000000" } \`\`\` {{recentMessages}} -Extract the following information about the requested token swap: -- Sell token address -- Buy token address -- Amount to sell (in wei) +Extract the following information about the requested token deployment: +- Token Name +- Token Symbol +- Token Owner +- Token initial supply Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.`; export const deployToken: Action = { - name: "EXECUTE_STARKNET_SWAP", + name: "DEPLOY_STARKNET_UNRUGGABLE_MEME_TOKEN", similes: [ - "STARKNET_SWAP_TOKENS", - "STARKNET_TOKEN_SWAP", - "STARKNET_TRADE_TOKENS", - "STARKNET_EXCHANGE_TOKENS", + "DEPLOY_STARKNET_UNRUGGABLE_TOKEN", + "STARKNET_DEPLOY_MEMECOIN", + "STARKNET_CREATE_MEMECOIN", ], validate: async (runtime: IAgentRuntime, message: Memory) => { return validateSettings(runtime); }, description: - "Perform a token swap on starknet. Use this action when a user asks you to swap tokens anything.", + "Deploy an Unruggable Memecoin on Starknet. Use this action when a user asks you to deploy a new token on Starknet.", handler: async ( runtime: IAgentRuntime, message: Memory, @@ -93,62 +89,74 @@ export const deployToken: Action = { _options: { [key: string]: unknown }, callback?: HandlerCallback ): Promise => { - elizaLogger.log("Starting EXECUTE_STARKNET_SWAP handler..."); + elizaLogger.log( + "Starting DEPLOY_STARKNET_UNRUGGABLE_MEME_TOKEN handler..." + ); if (!state) { state = (await runtime.composeState(message)) as State; } else { state = await runtime.updateRecentMessageState(state); } - const swapContext = composeContext({ + const deployContext = composeContext({ state, - template: swapTemplate, + template: deployTemplate, }); const response = await generateObject({ runtime, - context: swapContext, + context: deployContext, modelClass: ModelClass.MEDIUM, }); + elizaLogger.log("init supply." + response.initialSupply); + elizaLogger.log(response); - if (!isSwapContent(response)) { - callback?.({ text: "Invalid swap content, please try again." }); + if (!isDeployTokenContent(response)) { + callback?.({ + text: "Invalid deployment content, please try again.", + }); return false; } try { - // Get quote - const quoteParams: QuoteRequest = { - sellTokenAddress: response.sellTokenAddress, - buyTokenAddress: response.buyTokenAddress, - sellAmount: BigInt(response.sellAmount), - }; - - const quote = await fetchQuotes(quoteParams); - - // Execute swap - const swapResult = await executeAvnuSwap( - getStarknetAccount(runtime), - quote[0], - { - slippage: 0.05, // 5% slippage - executeApprove: true, - } - ); + const provider = getStarknetProvider(runtime); + const account = getStarknetAccount(runtime); + + const factory = new Factory({ + provider, + chainId: await provider.getChainId(), + }); + + const { tokenAddress, calls } = factory.getDeployCalldata({ + name: response.name, + symbol: response.symbol, + owner: response.owner, + initialSupply: response.initialSupply, + }); elizaLogger.log( - "Swap completed successfully!" + swapResult.transactionHash + "Deployment has been initiated for coin: " + + response.name + + " at address: " + + tokenAddress ); + const tx = await account.execute(calls); + callback?.({ text: - "Swap completed successfully! tx: " + - swapResult.transactionHash, + "Token Deployment completed successfully!" + + response.symbol + + " deployed in tx: " + + tx.transaction_hash, }); return true; } catch (error) { - elizaLogger.error("Error during token swap:", error); - callback?.({ text: `Error during swap:` }); + elizaLogger.error("Error during token deployment:", error); + callback?.({ + text: `Error during deployment: ${error.message}`, + content: { error: error.message }, + }); return false; } }, @@ -157,13 +165,13 @@ export const deployToken: Action = { { user: "{{user1}}", content: { - text: "Swap 10 ETH for LORDS", + text: "Deploy a new token called Lords with the symbol LORDS, owned by 0x024BA6a4023fB90962bDfc2314F3B94372aa382D155291635fc3E6b777657A5B and initial supply of 1000000000000000000 on Starknet", }, }, { user: "{{agent}}", content: { - text: "Ok, I'll swap 10 ETH for LORDS", + text: "Ok, I'll deploy the Lords token to Starknet", }, }, ], @@ -171,13 +179,13 @@ export const deployToken: Action = { { user: "{{user1}}", content: { - text: "Swap 100 $lords on starknet", + text: "Deploy the SLINK coin to Starknet", }, }, { user: "{{agent}}", content: { - text: "Ok, I'll swap 100 $lords on starknet", + text: "Ok, I'll deploy your coin on Starknet", }, }, ], @@ -185,13 +193,13 @@ export const deployToken: Action = { { user: "{{user1}}", content: { - text: "Swap 0.5 BTC for LORDS", + text: "Create a new coin on Starknet", }, }, { user: "{{agent}}", content: { - text: "Ok, I'll swap 0.5 BTC for LORDS", + text: "Ok, I'll create a new coin for you on Starknet", }, }, ], diff --git a/packages/plugin-starknet/src/index.ts b/packages/plugin-starknet/src/index.ts index 5ed9e48db70..30211f77d7e 100644 --- a/packages/plugin-starknet/src/index.ts +++ b/packages/plugin-starknet/src/index.ts @@ -1,7 +1,7 @@ import { Plugin } from "@ai16z/eliza"; import { executeSwap } from "./actions/swap"; import transfer from "./actions/transfer"; - +import { deployToken } from "./actions/unruggable"; export const PROVIDER_CONFIG = { AVNU_API: "https://starknet.impulse.avnu.fi/v1", MAX_RETRIES: 3, @@ -20,7 +20,7 @@ export const PROVIDER_CONFIG = { export const starknetPlugin: Plugin = { name: "starknet", description: "Starknet Plugin for Eliza", - actions: [transfer, executeSwap], + actions: [transfer, executeSwap, deployToken], evaluators: [], providers: [], }; diff --git a/packages/plugin-video-generation/package.json b/packages/plugin-video-generation/package.json new file mode 100644 index 00000000000..c311e78d2e2 --- /dev/null +++ b/packages/plugin-video-generation/package.json @@ -0,0 +1,18 @@ +{ + "name": "@ai16z/plugin-video-generation", + "version": "0.0.1", + "main": "dist/index.js", + "type": "module", + "types": "dist/index.d.ts", + "dependencies": { + "@ai16z/eliza": "workspace:*", + "tsup": "^8.3.5" + }, + "scripts": { + "build": "tsup --format esm --dts", + "dev": "tsup --watch" + }, + "peerDependencies": { + "whatwg-url": "7.1.0" + } +} \ No newline at end of file diff --git a/packages/plugin-video-generation/src/constants.ts b/packages/plugin-video-generation/src/constants.ts new file mode 100644 index 00000000000..4f7428d8f76 --- /dev/null +++ b/packages/plugin-video-generation/src/constants.ts @@ -0,0 +1,4 @@ +export const LUMA_CONSTANTS = { + API_URL: 'https://api.lumalabs.ai/dream-machine/v1/generations', + API_KEY_SETTING: "LUMA_API_KEY" // The setting name to fetch from runtime +}; \ No newline at end of file diff --git a/packages/plugin-video-generation/src/index.ts b/packages/plugin-video-generation/src/index.ts new file mode 100644 index 00000000000..0723485263b --- /dev/null +++ b/packages/plugin-video-generation/src/index.ts @@ -0,0 +1,221 @@ +import { elizaLogger } from "@ai16z/eliza/src/logger.ts"; +import { + Action, + HandlerCallback, + IAgentRuntime, + Memory, + Plugin, + State, +} from "@ai16z/eliza/src/types.ts"; +import fs from "fs"; +import { LUMA_CONSTANTS } from './constants'; + +const generateVideo = async (prompt: string, runtime: IAgentRuntime) => { + const API_KEY = runtime.getSetting(LUMA_CONSTANTS.API_KEY_SETTING); + + try { + elizaLogger.log("Starting video generation with prompt:", prompt); + + const response = await fetch(LUMA_CONSTANTS.API_URL, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${API_KEY}`, + 'accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ prompt }) + }); + + if (!response.ok) { + const errorText = await response.text(); + elizaLogger.error("Luma API error:", { + status: response.status, + statusText: response.statusText, + error: errorText + }); + throw new Error(`Luma API error: ${response.statusText} - ${errorText}`); + } + + const data = await response.json(); + elizaLogger.log("Generation request successful, received response:", data); + + // Poll for completion + let status = data.status; + let videoUrl = null; + const generationId = data.id; + + while (status !== 'completed' && status !== 'failed') { + await new Promise(resolve => setTimeout(resolve, 5000)); // Wait 5 seconds + + const statusResponse = await fetch(`${LUMA_CONSTANTS.API_URL}/${generationId}`, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${API_KEY}`, + 'accept': 'application/json' + } + }); + + if (!statusResponse.ok) { + const errorText = await statusResponse.text(); + elizaLogger.error("Status check error:", { + status: statusResponse.status, + statusText: statusResponse.statusText, + error: errorText + }); + throw new Error('Failed to check generation status: ' + errorText); + } + + const statusData = await statusResponse.json(); + elizaLogger.log("Status check response:", statusData); + + status = statusData.state; + if (status === 'completed') { + videoUrl = statusData.assets?.video; + } + } + + if (status === 'failed') { + throw new Error('Video generation failed'); + } + + if (!videoUrl) { + throw new Error('No video URL in completed response'); + } + + return { + success: true, + data: videoUrl + }; + } catch (error) { + elizaLogger.error("Video generation error:", error); + return { + success: false, + error: error.message || 'Unknown error occurred' + }; + } +} + +const videoGeneration: Action = { + name: "GENERATE_VIDEO", + similes: [ + "VIDEO_GENERATION", + "VIDEO_GEN", + "CREATE_VIDEO", + "MAKE_VIDEO", + "RENDER_VIDEO", + "ANIMATE", + "CREATE_ANIMATION", + "VIDEO_CREATE", + "VIDEO_MAKE" + ], + description: "Generate a video based on a text prompt", + validate: async (runtime: IAgentRuntime, message: Memory) => { + elizaLogger.log("Validating video generation action"); + const lumaApiKey = runtime.getSetting("LUMA_API_KEY"); + elizaLogger.log("LUMA_API_KEY present:", !!lumaApiKey); + return !!lumaApiKey; + }, + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + options: any, + callback: HandlerCallback + ) => { + elizaLogger.log("Video generation request:", message); + + // Clean up the prompt by removing mentions and commands + let videoPrompt = message.content.text + .replace(/<@\d+>/g, '') // Remove mentions + .replace(/generate video|create video|make video|render video/gi, '') // Remove commands + .trim(); + + if (!videoPrompt || videoPrompt.length < 5) { + callback({ + text: "Could you please provide more details about what kind of video you'd like me to generate? For example: 'Generate a video of a sunset on a beach' or 'Create a video of a futuristic city'", + }); + return; + } + + elizaLogger.log("Video prompt:", videoPrompt); + + callback({ + text: `I'll generate a video based on your prompt: "${videoPrompt}". This might take a few minutes...`, + }); + + try { + const result = await generateVideo(videoPrompt, runtime); + + if (result.success && result.data) { + // Download the video file + const response = await fetch(result.data); + const arrayBuffer = await response.arrayBuffer(); + const videoFileName = `content_cache/generated_video_${Date.now()}.mp4`; + + // Save video file + fs.writeFileSync(videoFileName, Buffer.from(arrayBuffer)); + + callback({ + text: "Here's your generated video!", + attachments: [ + { + id: crypto.randomUUID(), + url: result.data, + title: "Generated Video", + source: "videoGeneration", + description: videoPrompt, + text: videoPrompt, + }, + ], + }, [videoFileName]); // Add the video file to the attachments + } else { + callback({ + text: `Video generation failed: ${result.error}`, + error: true + }); + } + } catch (error) { + elizaLogger.error(`Failed to generate video. Error: ${error}`); + callback({ + text: `Video generation failed: ${error.message}`, + error: true + }); + } + }, + examples: [ + [ + { + user: "{{user1}}", + content: { text: "Generate a video of a cat playing piano" }, + }, + { + user: "{{agentName}}", + content: { + text: "I'll create a video of a cat playing piano for you", + action: "GENERATE_VIDEO" + }, + } + ], + [ + { + user: "{{user1}}", + content: { text: "Can you make a video of a sunset at the beach?" }, + }, + { + user: "{{agentName}}", + content: { + text: "I'll generate a beautiful beach sunset video for you", + action: "GENERATE_VIDEO" + }, + } + ] + ] +} as Action; + +export const videoGenerationPlugin: Plugin = { + name: "videoGeneration", + description: "Generate videos using Luma AI", + actions: [videoGeneration], + evaluators: [], + providers: [], +}; \ No newline at end of file diff --git a/packages/plugin-video-generation/tsconfig.json b/packages/plugin-video-generation/tsconfig.json new file mode 100644 index 00000000000..c065d9145a4 --- /dev/null +++ b/packages/plugin-video-generation/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": ".", + "module": "ESNext", + "moduleResolution": "Bundler", + "types": [ + "node" + ] + }, + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/packages/plugin-video-generation/tsup.config.ts b/packages/plugin-video-generation/tsup.config.ts new file mode 100644 index 00000000000..4b66bbcbbde --- /dev/null +++ b/packages/plugin-video-generation/tsup.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + outDir: "dist", + sourcemap: true, + clean: true, + format: ["esm"], + external: [ + "dotenv", + "fs", + "path", + "@reflink/reflink", + "@node-llama-cpp", + "https", + "http", + "agentkeepalive" + ], +}); \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a97848a2ed5..692f2dd4493 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3050,8 +3050,8 @@ packages: cpu: [x64] os: [win32] - '@octokit/app@15.1.0': - resolution: {integrity: sha512-TkBr7QgOmE6ORxvIAhDbZsqPkF7RSqTY4pLTtUQCvr6dTXqvi2fFo46q3h1lxlk/sGMQjqyZ0kEahkD/NyzOHg==} + '@octokit/app@15.1.1': + resolution: {integrity: sha512-fk8xrCSPTJGpyBdBNI+DcZ224dm0aApv4vi6X7/zTmANXlegKV2Td+dJ+fd7APPaPN7R+xttUsj2Fm+AFDSfMQ==} engines: {node: '>= 18'} '@octokit/auth-app@7.1.3': @@ -8380,8 +8380,8 @@ packages: resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==} engines: {node: '>= 12.13.0'} - local-pkg@0.5.0: - resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + local-pkg@0.5.1: + resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} engines: {node: '>=14'} locate-character@3.0.0: @@ -15032,7 +15032,7 @@ snapshots: '@iconify/types': 2.0.0 debug: 4.3.7(supports-color@5.5.0) kolorist: 1.8.0 - local-pkg: 0.5.0 + local-pkg: 0.5.1 mlly: 1.7.3 transitivePeerDependencies: - supports-color @@ -15720,7 +15720,7 @@ snapshots: '@nx/nx-win32-x64-msvc@20.1.2': optional: true - '@octokit/app@15.1.0': + '@octokit/app@15.1.1': dependencies: '@octokit/auth-app': 7.1.3 '@octokit/auth-unauthenticated': 6.1.0 @@ -22118,7 +22118,7 @@ snapshots: loader-utils@3.3.1: {} - local-pkg@0.5.0: + local-pkg@0.5.1: dependencies: mlly: 1.7.3 pkg-types: 1.2.1 @@ -22169,7 +22169,7 @@ snapshots: log-symbols@4.1.0: dependencies: - chalk: 4.1.0 + chalk: 4.1.2 is-unicode-supported: 0.1.0 log-symbols@6.0.0: @@ -23440,7 +23440,7 @@ snapshots: octokit@4.0.2: dependencies: - '@octokit/app': 15.1.0 + '@octokit/app': 15.1.1 '@octokit/core': 6.1.2 '@octokit/oauth-app': 7.1.3 '@octokit/plugin-paginate-graphql': 5.2.4(@octokit/core@6.1.2)