diff --git a/.github/workflows/integrationTests.yaml b/.github/workflows/integrationTests.yaml index 0dcef61c065..9636b00986c 100644 --- a/.github/workflows/integrationTests.yaml +++ b/.github/workflows/integrationTests.yaml @@ -1,19 +1,21 @@ name: Integration Tests + on: - push: - branches: - - "*" - pull_request_target: - branches: - - "*" + push: + branches: + - "*" + pull_request_target: + branches: + - "*" jobs: - integration-tests: - runs-on: ubuntu-latest - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - steps: - - uses: actions/checkout@v4 + integration-tests: + runs-on: ubuntu-latest + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + steps: + - name: Checkout code + uses: actions/checkout@v4 - uses: pnpm/action-setup@v3 with: @@ -24,21 +26,24 @@ jobs: node-version: "23.3.0" cache: "pnpm" - - name: Clean up - run: pnpm clean + - name: Clean up previous builds and caches + run: | + pnpm store prune + pnpm clean + rm -rf node_modules .pnpm-store packages/*/node_modules # Removes all node_modules directories - - name: Install dependencies - run: pnpm install -r --no-frozen-lockfile + - name: Install dependencies + run: pnpm install -r --no-frozen-lockfile - - name: Build packages - run: pnpm build + - name: Build packages + run: pnpm build - - name: Check for API key - run: | - if [ -z "$OPENAI_API_KEY" ]; then - echo "Error: OPENAI_API_KEY is not set." - exit 1 - fi + - name: Check for API key + run: | + if [ -z "$OPENAI_API_KEY" ]; then + echo "Error: OPENAI_API_KEY is not set." + exit 1 + fi - - name: Run integration tests - run: pnpm run integrationTests + - name: Run integration tests + run: pnpm run integrationTests \ No newline at end of file diff --git a/agent/package.json b/agent/package.json index eb4a23a2752..494ef7f205b 100644 --- a/agent/package.json +++ b/agent/package.json @@ -67,6 +67,7 @@ "@elizaos/plugin-fuel": "workspace:*", "@elizaos/plugin-avalanche": "workspace:*", "@elizaos/plugin-web-search": "workspace:*", + "@elizaos/plugin-twilio": "workspace:*", "@elizaos/plugin-genlayer": "workspace:*", "@elizaos/plugin-open-weather": "workspace:*", "readline": "1.3.0", diff --git a/agent/src/index.ts b/agent/src/index.ts index 296d0313fea..770c8c699c4 100644 --- a/agent/src/index.ts +++ b/agent/src/index.ts @@ -554,6 +554,12 @@ export async function createAgent( // character.plugins are handled when clients are added plugins: [ bootstrapPlugin, + getSecret(character, "TWILIO_ACCOUNT_SID") && + getSecret(character, "TWILIO_AUTH_TOKEN") && + getSecret(character, "TWILIO_PHONE_NUMBER") && + getSecret(character, "TWILIO_WHATSAPP_PHONE_NUMBER") + ? twilioPlugin + : null, getSecret(character, "CONFLUX_CORE_PRIVATE_KEY") ? confluxPlugin : null, diff --git a/packages/core/models.ts b/packages/core/models.ts deleted file mode 100644 index 67269b49d37..00000000000 --- a/packages/core/models.ts +++ /dev/null @@ -1,542 +0,0 @@ -import settings from "./settings.ts"; -import { Models, ModelProviderName, ModelClass } from "./types.ts"; - -export const models: Models = { - [ModelProviderName.OPENAI]: { - endpoint: settings.OPENAI_API_URL || "https://api.openai.com/v1", - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - frequency_penalty: 0.0, - presence_penalty: 0.0, - temperature: 0.6, - }, - model: { - [ModelClass.SMALL]: settings.SMALL_OPENAI_MODEL || "gpt-4o-mini", - [ModelClass.MEDIUM]: settings.MEDIUM_OPENAI_MODEL || "gpt-4o", - [ModelClass.LARGE]: settings.LARGE_OPENAI_MODEL || "gpt-4o", - [ModelClass.EMBEDDING]: settings.EMBEDDING_OPENAI_MODEL || "text-embedding-3-small", - [ModelClass.IMAGE]: settings.IMAGE_OPENAI_MODEL || "dall-e-3", - }, - }, - [ModelProviderName.ETERNALAI]: { - endpoint: settings.ETERNALAI_URL, - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - frequency_penalty: 0.0, - presence_penalty: 0.0, - temperature: 0.6, - }, - model: { - [ModelClass.SMALL]: - settings.ETERNALAI_MODEL || - "neuralmagic/Meta-Llama-3.1-405B-Instruct-quantized.w4a16", - [ModelClass.MEDIUM]: - settings.ETERNALAI_MODEL || - "neuralmagic/Meta-Llama-3.1-405B-Instruct-quantized.w4a16", - [ModelClass.LARGE]: - settings.ETERNALAI_MODEL || - "neuralmagic/Meta-Llama-3.1-405B-Instruct-quantized.w4a16", - [ModelClass.EMBEDDING]: "", - [ModelClass.IMAGE]: "", - }, - }, - [ModelProviderName.ANTHROPIC]: { - settings: { - stop: [], - maxInputTokens: 200000, - maxOutputTokens: 4096, - frequency_penalty: 0.4, - presence_penalty: 0.4, - temperature: 0.7, - }, - endpoint: "https://api.anthropic.com/v1", - model: { - [ModelClass.SMALL]: settings.SMALL_ANTHROPIC_MODEL || "claude-3-haiku-20240307", - [ModelClass.MEDIUM]: settings.MEDIUM_ANTHROPIC_MODEL || "claude-3-5-sonnet-20241022", - [ModelClass.LARGE]: settings.LARGE_ANTHROPIC_MODEL || "claude-3-5-sonnet-20241022", - }, - }, - [ModelProviderName.CLAUDE_VERTEX]: { - settings: { - stop: [], - maxInputTokens: 200000, - maxOutputTokens: 8192, - frequency_penalty: 0.4, - presence_penalty: 0.4, - temperature: 0.7, - }, - endpoint: "https://api.anthropic.com/v1", // TODO: check - model: { - [ModelClass.SMALL]: "claude-3-5-sonnet-20241022", - [ModelClass.MEDIUM]: "claude-3-5-sonnet-20241022", - [ModelClass.LARGE]: "claude-3-opus-20240229", - }, - }, - [ModelProviderName.GROK]: { - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - frequency_penalty: 0.4, - presence_penalty: 0.4, - temperature: 0.7, - }, - endpoint: "https://api.x.ai/v1", - model: { - [ModelClass.SMALL]: settings.SMALL_GROK_MODEL || "grok-2-1212", - [ModelClass.MEDIUM]: settings.MEDIUM_GROK_MODEL || "grok-2-1212", - [ModelClass.LARGE]: settings.LARGE_GROK_MODEL || "grok-2-1212", - [ModelClass.EMBEDDING]: settings.EMBEDDING_GROK_MODEL || "grok-2-1212", // not sure about this one - }, - }, - [ModelProviderName.GROQ]: { - endpoint: "https://api.groq.com/openai/v1", - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8000, - frequency_penalty: 0.4, - presence_penalty: 0.4, - temperature: 0.7, - }, - model: { - [ModelClass.SMALL]: - settings.SMALL_GROQ_MODEL || "llama-3.1-8b-instant", - [ModelClass.MEDIUM]: - settings.MEDIUM_GROQ_MODEL || "llama-3.3-70b-versatile", - [ModelClass.LARGE]: - settings.LARGE_GROQ_MODEL || "llama-3.2-90b-vision-preview", - [ModelClass.EMBEDDING]: - settings.EMBEDDING_GROQ_MODEL || "llama-3.1-8b-instant", - }, - }, - [ModelProviderName.LLAMACLOUD]: { - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - repetition_penalty: 0.4, - temperature: 0.7, - }, - imageSettings: { - steps: 4, - }, - endpoint: "https://api.llamacloud.com/v1", - model: { - [ModelClass.SMALL]: "meta-llama/Llama-3.2-3B-Instruct-Turbo", - [ModelClass.MEDIUM]: "meta-llama-3.1-8b-instruct", - [ModelClass.LARGE]: "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo", - [ModelClass.EMBEDDING]: - "togethercomputer/m2-bert-80M-32k-retrieval", - [ModelClass.IMAGE]: "black-forest-labs/FLUX.1-schnell", - }, - }, - [ModelProviderName.TOGETHER]: { - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - repetition_penalty: 0.4, - temperature: 0.7, - }, - imageSettings: { - steps: 4, - }, - endpoint: "https://api.together.ai/v1", - model: { - [ModelClass.SMALL]: "meta-llama/Llama-3.2-3B-Instruct-Turbo", - [ModelClass.MEDIUM]: "meta-llama-3.1-8b-instruct", - [ModelClass.LARGE]: "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo", - [ModelClass.EMBEDDING]: - "togethercomputer/m2-bert-80M-32k-retrieval", - [ModelClass.IMAGE]: "black-forest-labs/FLUX.1-schnell", - }, - }, - [ModelProviderName.LLAMALOCAL]: { - settings: { - stop: ["<|eot_id|>", "<|eom_id|>"], - maxInputTokens: 32768, - maxOutputTokens: 8192, - repetition_penalty: 0.4, - temperature: 0.7, - }, - model: { - [ModelClass.SMALL]: - "NousResearch/Hermes-3-Llama-3.1-8B-GGUF/resolve/main/Hermes-3-Llama-3.1-8B.Q8_0.gguf?download=true", - [ModelClass.MEDIUM]: - "NousResearch/Hermes-3-Llama-3.1-8B-GGUF/resolve/main/Hermes-3-Llama-3.1-8B.Q8_0.gguf?download=true", // TODO: ?download=true - [ModelClass.LARGE]: - "NousResearch/Hermes-3-Llama-3.1-8B-GGUF/resolve/main/Hermes-3-Llama-3.1-8B.Q8_0.gguf?download=true", - // "RichardErkhov/NousResearch_-_Meta-Llama-3.1-70B-gguf", // TODO: - [ModelClass.EMBEDDING]: - "togethercomputer/m2-bert-80M-32k-retrieval", - }, - }, - [ModelProviderName.GOOGLE]: { - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - frequency_penalty: 0.4, - presence_penalty: 0.4, - temperature: 0.7, - }, - model: { - [ModelClass.SMALL]: - settings.SMALL_GOOGLE_MODEL || - settings.GOOGLE_MODEL || - "gemini-2.0-flash-exp", - [ModelClass.MEDIUM]: - settings.MEDIUM_GOOGLE_MODEL || - settings.GOOGLE_MODEL || - "gemini-2.0-flash-exp", - [ModelClass.LARGE]: - settings.LARGE_GOOGLE_MODEL || - settings.GOOGLE_MODEL || - "gemini-2.0-flash-exp", - [ModelClass.EMBEDDING]: - settings.EMBEDDING_GOOGLE_MODEL || - settings.GOOGLE_MODEL || - "text-embedding-004", - }, - }, - [ModelProviderName.REDPILL]: { - endpoint: "https://api.red-pill.ai/v1", - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - frequency_penalty: 0.0, - presence_penalty: 0.0, - temperature: 0.6, - }, - // Available models: https://docs.red-pill.ai/get-started/supported-models - // To test other models, change the models below - model: { - [ModelClass.SMALL]: - settings.SMALL_REDPILL_MODEL || - settings.REDPILL_MODEL || - "gpt-4o-mini", - [ModelClass.MEDIUM]: - settings.MEDIUM_REDPILL_MODEL || - settings.REDPILL_MODEL || - "gpt-4o", - [ModelClass.LARGE]: - settings.LARGE_REDPILL_MODEL || - settings.REDPILL_MODEL || - "gpt-4o", - [ModelClass.EMBEDDING]: "text-embedding-3-small", - }, - }, - [ModelProviderName.OPENROUTER]: { - endpoint: "https://openrouter.ai/api/v1", - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - frequency_penalty: 0.4, - presence_penalty: 0.4, - temperature: 0.7, - }, - // Available models: https://openrouter.ai/models - // To test other models, change the models below - model: { - [ModelClass.SMALL]: - settings.SMALL_OPENROUTER_MODEL || - settings.OPENROUTER_MODEL || - "nousresearch/hermes-3-llama-3.1-405b", - [ModelClass.MEDIUM]: - settings.MEDIUM_OPENROUTER_MODEL || - settings.OPENROUTER_MODEL || - "nousresearch/hermes-3-llama-3.1-405b", - [ModelClass.LARGE]: - settings.LARGE_OPENROUTER_MODEL || - settings.OPENROUTER_MODEL || - "nousresearch/hermes-3-llama-3.1-405b", - [ModelClass.EMBEDDING]: "text-embedding-3-small", - }, - }, - [ModelProviderName.OLLAMA]: { - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - frequency_penalty: 0.4, - presence_penalty: 0.4, - temperature: 0.7, - }, - endpoint: settings.OLLAMA_SERVER_URL || "http://localhost:11434", - model: { - [ModelClass.SMALL]: - settings.SMALL_OLLAMA_MODEL || - settings.OLLAMA_MODEL || - "llama3.2", - [ModelClass.MEDIUM]: - settings.MEDIUM_OLLAMA_MODEL || - settings.OLLAMA_MODEL || - "hermes3", - [ModelClass.LARGE]: - settings.LARGE_OLLAMA_MODEL || - settings.OLLAMA_MODEL || - "hermes3:70b", - [ModelClass.EMBEDDING]: - settings.OLLAMA_EMBEDDING_MODEL || "mxbai-embed-large", - }, - }, - [ModelProviderName.HEURIST]: { - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - repetition_penalty: 0.4, - temperature: 0.7, - }, - imageSettings: { - steps: 20, - }, - endpoint: "https://llm-gateway.heurist.xyz", - model: { - [ModelClass.SMALL]: - settings.SMALL_HEURIST_MODEL || - "meta-llama/llama-3-70b-instruct", - [ModelClass.MEDIUM]: - settings.MEDIUM_HEURIST_MODEL || - "meta-llama/llama-3-70b-instruct", - [ModelClass.LARGE]: - settings.LARGE_HEURIST_MODEL || - "meta-llama/llama-3.1-405b-instruct", - [ModelClass.EMBEDDING]: "", //Add later, - [ModelClass.IMAGE]: settings.HEURIST_IMAGE_MODEL || "PepeXL", - }, - }, - [ModelProviderName.GALADRIEL]: { - endpoint: "https://api.galadriel.com/v1", - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - frequency_penalty: 0.5, - presence_penalty: 0.5, - temperature: 0.8, - }, - model: { - [ModelClass.SMALL]: "llama3.1:70b", - [ModelClass.MEDIUM]: "llama3.1:70b", - [ModelClass.LARGE]: "llama3.1:405b", - [ModelClass.EMBEDDING]: "gte-large-en-v1.5", - [ModelClass.IMAGE]: "stabilityai/stable-diffusion-xl-base-1.0", - }, - }, - [ModelProviderName.FAL]: { - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - repetition_penalty: 0.4, - temperature: 0.7, - }, - imageSettings: { - steps: 28, - }, - endpoint: "https://api.fal.ai/v1", - model: { - [ModelClass.SMALL]: "", // FAL doesn't provide text models - [ModelClass.MEDIUM]: "", - [ModelClass.LARGE]: "", - [ModelClass.EMBEDDING]: "", - [ModelClass.IMAGE]: "fal-ai/flux-lora", - }, - }, - [ModelProviderName.GAIANET]: { - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - repetition_penalty: 0.4, - temperature: 0.7, - }, - endpoint: settings.GAIANET_SERVER_URL, - model: { - [ModelClass.SMALL]: - settings.GAIANET_MODEL || - settings.SMALL_GAIANET_MODEL || - "llama3b", - [ModelClass.MEDIUM]: - settings.GAIANET_MODEL || - settings.MEDIUM_GAIANET_MODEL || - "llama", - [ModelClass.LARGE]: - settings.GAIANET_MODEL || - settings.LARGE_GAIANET_MODEL || - "qwen72b", - [ModelClass.EMBEDDING]: - settings.GAIANET_EMBEDDING_MODEL || "nomic-embed", - }, - }, - [ModelProviderName.ALI_BAILIAN]: { - endpoint: "https://dashscope.aliyuncs.com/compatible-mode/v1", - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - frequency_penalty: 0.4, - presence_penalty: 0.4, - temperature: 0.6, - }, - model: { - [ModelClass.SMALL]: "qwen-turbo", - [ModelClass.MEDIUM]: "qwen-plus", - [ModelClass.LARGE]: "qwen-max", - [ModelClass.IMAGE]: "wanx-v1", - }, - }, - [ModelProviderName.VOLENGINE]: { - endpoint: settings.VOLENGINE_API_URL || "https://open.volcengineapi.com/api/v3/", - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - frequency_penalty: 0.4, - presence_penalty: 0.4, - temperature: 0.6, - }, - model: { - [ModelClass.SMALL]: - settings.SMALL_VOLENGINE_MODEL || - settings.VOLENGINE_MODEL || - "doubao-lite-128k", - [ModelClass.MEDIUM]: - settings.MEDIUM_VOLENGINE_MODEL || - settings.VOLENGINE_MODEL || - "doubao-pro-128k", - [ModelClass.LARGE]: - settings.LARGE_VOLENGINE_MODEL || - settings.VOLENGINE_MODEL || - "doubao-pro-256k", - [ModelClass.EMBEDDING]: - settings.VOLENGINE_EMBEDDING_MODEL || - "doubao-embedding", - }, - }, - [ModelProviderName.NANOGPT]: { - endpoint: "https://nano-gpt.com/api/v1", - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - frequency_penalty: 0.0, - presence_penalty: 0.0, - temperature: 0.6, - }, - model: { - [ModelClass.SMALL]: settings.SMALL_NANOGPT_MODEL || "gpt-4o-mini", - [ModelClass.MEDIUM]: settings.MEDIUM_NANOGPT_MODEL || "gpt-4o", - [ModelClass.LARGE]: settings.LARGE_NANOGPT_MODEL || "gpt-4o", - } - }, - [ModelProviderName.HYPERBOLIC]: { - endpoint: "https://api.hyperbolic.xyz/v1", - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - temperature: 0.6, - }, - model: { - [ModelClass.SMALL]: - settings.SMALL_HYPERBOLIC_MODEL || - settings.HYPERBOLIC_MODEL || - "meta-llama/Llama-3.2-3B-Instruct", - [ModelClass.MEDIUM]: - settings.MEDIUM_HYPERBOLIC_MODEL || - settings.HYPERBOLIC_MODEL || - "meta-llama/Meta-Llama-3.1-70B-Instruct", - [ModelClass.LARGE]: - settings.LARGE_HYPERBOLIC_MODEL || - settings.HYPERBOLIC_MODEL || - "meta-llama/Meta-Llama-3.1-405-Instruct", - [ModelClass.IMAGE]: settings.IMAGE_HYPERBOLIC_MODEL || "FLUX.1-dev", - }, - }, - [ModelProviderName.VENICE]: { - endpoint: "https://api.venice.ai/api/v1", - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - temperature: 0.6, - }, - model: { - [ModelClass.SMALL]: settings.SMALL_VENICE_MODEL || "llama-3.3-70b", - [ModelClass.MEDIUM]: settings.MEDIUM_VENICE_MODEL || "llama-3.3-70b", - [ModelClass.LARGE]: settings.LARGE_VENICE_MODEL || "llama-3.1-405b", - [ModelClass.IMAGE]: settings.IMAGE_VENICE_MODEL || "fluently-xl", - }, - }, - [ModelProviderName.AKASH_CHAT_API]: { - endpoint: "https://chatapi.akash.network/api/v1", - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - temperature: 0.6, - }, - model: { - [ModelClass.SMALL]: - settings.SMALL_AKASH_CHAT_API_MODEL || - "Meta-Llama-3-2-3B-Instruct", - [ModelClass.MEDIUM]: - settings.MEDIUM_AKASH_CHAT_API_MODEL || - "Meta-Llama-3-3-70B-Instruct", - [ModelClass.LARGE]: - settings.LARGE_AKASH_CHAT_API_MODEL || - "Meta-Llama-3-1-405B-Instruct-FP8", - }, - }, - [ModelProviderName.LIVEPEER]: { - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - repetition_penalty: 0.4, - temperature: 0.7, - }, - // livepeer endpoint is handled from the sdk - model: { - [ModelClass.SMALL]: "", - [ModelClass.MEDIUM]: "", - [ModelClass.LARGE]: "", - [ModelClass.EMBEDDING]: "", - [ModelClass.IMAGE]: settings.LIVEPEER_IMAGE_MODEL || "ByteDance/SDXL-Lightning", - }, - }, - [ModelProviderName.INFERA]: { - endpoint: "https://api.infera.org", - settings: { - stop: [], - maxInputTokens: 128000, - maxOutputTokens: 8192, - temperature: 0.6, - }, - model: { - [ModelClass.SMALL]: - settings.SMALL_INFERA_MODEL || "llama3.2:3b", - [ModelClass.MEDIUM]: - settings.MEDIUM_INFERA_MODEL || "mistral-nemo:latest", - [ModelClass.LARGE]: - settings.LARGE_INFERA_MODEL || "mistral-small:latest", - }, - }, -}; - -export function getModel(provider: ModelProviderName, type: ModelClass) { - return models[provider].model[type]; -} - -export function getEndpoint(provider: ModelProviderName) { - return models[provider].endpoint; -} diff --git a/packages/core/package.json b/packages/core/package.json index 3a1b74388fe..d7369289147 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -35,7 +35,7 @@ "@eslint/js": "9.16.0", "@rollup/plugin-commonjs": "25.0.8", "@rollup/plugin-json": "6.1.0", - "@rollup/plugin-node-resolve": "15.3.0", + "@rollup/plugin-node-resolve": "16.0.0", "@rollup/plugin-replace": "5.0.7", "@rollup/plugin-terser": "0.1.0", "@rollup/plugin-typescript": "11.1.6", diff --git a/packages/core/src/database/CircuitBreaker.ts b/packages/core/src/database/CircuitBreaker.ts index 728cdf99b4c..b79b08daff0 100644 --- a/packages/core/src/database/CircuitBreaker.ts +++ b/packages/core/src/database/CircuitBreaker.ts @@ -53,10 +53,7 @@ export class CircuitBreaker { this.failureCount++; this.lastFailureTime = Date.now(); - if ( - this.state !== "OPEN" && - this.failureCount >= this.failureThreshold - ) { + if (this.state !== "OPEN" && this.failureCount >= this.failureThreshold) { this.state = "OPEN"; } } diff --git a/packages/core/src/environment.ts b/packages/core/src/environment.ts index 485a1e9d93c..0758d0d31d9 100644 --- a/packages/core/src/environment.ts +++ b/packages/core/src/environment.ts @@ -78,7 +78,10 @@ export const CharacterSchema = z.object({ adjectives: z.array(z.string()), knowledge: z.array(z.string()).optional(), clients: z.array(z.nativeEnum(Clients)), - plugins: z.union([z.array(z.string()), z.array(PluginSchema)]), + plugins: z.union([ + z.array(z.string()), + z.array(PluginSchema), + ]), settings: z .object({ secrets: z.record(z.string()).optional(), diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts index 7e04fa0b99a..1495b059e19 100644 --- a/packages/core/src/runtime.ts +++ b/packages/core/src/runtime.ts @@ -104,7 +104,8 @@ export class AgentRuntime implements IAgentRuntime { */ imageModelProvider: ModelProviderName; - /** + + /** * The model to use for describing images. */ imageVisionModelProvider: ModelProviderName; @@ -334,13 +335,14 @@ export class AgentRuntime implements IAgentRuntime { ); this.imageVisionModelProvider = - this.character.imageVisionModelProvider ?? this.modelProvider; + this.character.imageVisionModelProvider ?? this.modelProvider; elizaLogger.info("Selected model provider:", this.modelProvider); - elizaLogger.info( + elizaLogger.info( "Selected image model provider:", this.imageVisionModelProvider - ); + ); + // Validate model provider if (!Object.values(ModelProviderName).includes(this.modelProvider)) { @@ -430,27 +432,22 @@ export class AgentRuntime implements IAgentRuntime { } async stop() { - elizaLogger.debug("runtime::stop - character", this.character); - // stop services, they don't have a stop function + elizaLogger.debug('runtime::stop - character', this.character) + // stop services, they don't have a stop function // just initialize - // plugins + // plugins // have actions, providers, evaluators (no start/stop) // services (just initialized), clients - // client have a start - for (const cStr in this.clients) { - const c = this.clients[cStr]; - elizaLogger.log( - "runtime::stop - requesting", - cStr, - "client stop for", - this.character.name - ); - c.stop(); - } - // we don't need to unregister with directClient - // don't need to worry about knowledge + // client have a start + for(const cStr in this.clients) { + const c = this.clients[cStr] + elizaLogger.log('runtime::stop - requesting', cStr, 'client stop for', this.character.name) + c.stop() + } + // we don't need to unregister with directClient + // don't need to worry about knowledge } /** diff --git a/packages/core/src/test_resources/createRuntime.ts b/packages/core/src/test_resources/createRuntime.ts index 668fa47b5b2..209b800cbe2 100644 --- a/packages/core/src/test_resources/createRuntime.ts +++ b/packages/core/src/test_resources/createRuntime.ts @@ -19,7 +19,7 @@ import { User } from "./types.ts"; /** * Creates a runtime environment for the agent. - * + * * @param {Object} param - The parameters for creating the runtime. * @param {Record | NodeJS.ProcessEnv} [param.env] - The environment variables. * @param {number} [param.conversationLength] - The length of the conversation. diff --git a/packages/core/src/tests/actions.test.ts b/packages/core/src/tests/actions.test.ts index 1091fe195f5..ab0fcdfb915 100644 --- a/packages/core/src/tests/actions.test.ts +++ b/packages/core/src/tests/actions.test.ts @@ -20,23 +20,13 @@ describe("Actions", () => { }, ], [ - { - user: "user1", - content: { text: "Hey {{user2}}, how are you?" }, - }, - { - user: "user2", - content: { text: "I'm good {{user1}}, thanks!" }, - }, + { user: "user1", content: { text: "Hey {{user2}}, how are you?" } }, + { user: "user2", content: { text: "I'm good {{user1}}, thanks!" } }, ], ], similes: ["say hi", "welcome"], - handler: async () => { - throw new Error("Not implemented"); - }, - validate: async () => { - throw new Error("Not implemented"); - }, + handler: async () => { throw new Error("Not implemented"); }, + validate: async () => { throw new Error("Not implemented"); }, }, { name: "farewell", @@ -48,38 +38,24 @@ describe("Actions", () => { ], ], similes: ["say bye", "leave"], - handler: async () => { - throw new Error("Not implemented"); - }, - validate: async () => { - throw new Error("Not implemented"); - }, + handler: async () => { throw new Error("Not implemented"); }, + validate: async () => { throw new Error("Not implemented"); }, }, { name: "help", description: "Get assistance", examples: [ [ - { - user: "user1", - content: { text: "Can you help me {{user2}}?" }, - }, + { user: "user1", content: { text: "Can you help me {{user2}}?" } }, { user: "user2", - content: { - text: "Of course {{user1}}, what do you need?", - action: "assist", - }, + content: { text: "Of course {{user1}}, what do you need?", action: "assist" } }, ], ], similes: ["assist", "support"], - handler: async () => { - throw new Error("Not implemented"); - }, - validate: async () => { - throw new Error("Not implemented"); - }, + handler: async () => { throw new Error("Not implemented"); }, + validate: async () => { throw new Error("Not implemented"); }, }, ]; @@ -110,13 +86,8 @@ describe("Actions", () => { describe("formatActionNames", () => { it("should format action names correctly", () => { - const formatted = formatActionNames([ - mockActions[0], - mockActions[1], - ]); - expect(formatted).toMatch( - /^(greet|farewell)(, (greet|farewell))?$/ - ); + const formatted = formatActionNames([mockActions[0], mockActions[1]]); + expect(formatted).toMatch(/^(greet|farewell)(, (greet|farewell))?$/); }); it("should handle single action", () => { @@ -152,7 +123,7 @@ describe("Actions", () => { describe("Action Structure", () => { it("should validate action structure", () => { - mockActions.forEach((action) => { + mockActions.forEach(action => { expect(action).toHaveProperty("name"); expect(action).toHaveProperty("description"); expect(action).toHaveProperty("examples"); @@ -165,9 +136,9 @@ describe("Actions", () => { }); it("should validate example structure", () => { - mockActions.forEach((action) => { - action.examples.forEach((example) => { - example.forEach((message) => { + mockActions.forEach(action => { + action.examples.forEach(example => { + example.forEach(message => { expect(message).toHaveProperty("user"); expect(message).toHaveProperty("content"); expect(message.content).toHaveProperty("text"); @@ -177,7 +148,7 @@ describe("Actions", () => { }); it("should have unique action names", () => { - const names = mockActions.map((action) => action.name); + const names = mockActions.map(action => action.name); const uniqueNames = new Set(names); expect(names.length).toBe(uniqueNames.size); }); diff --git a/packages/core/src/tests/context.test.ts b/packages/core/src/tests/context.test.ts index 3c3bc978f9c..78e4bc274d3 100644 --- a/packages/core/src/tests/context.test.ts +++ b/packages/core/src/tests/context.test.ts @@ -250,7 +250,7 @@ describe("composeContext", () => { }); it("should handle missing values in handlebars template", () => { - const state = { ...baseState }; + const state = {...baseState} const template = "Hello, {{userName}}!"; const result = composeContext({ diff --git a/packages/core/src/tests/environment.test.ts b/packages/core/src/tests/environment.test.ts index fe690f4e0e1..f38b683919f 100644 --- a/packages/core/src/tests/environment.test.ts +++ b/packages/core/src/tests/environment.test.ts @@ -1,20 +1,20 @@ -import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import { validateEnv, validateCharacterConfig } from "../environment"; -import { Clients, ModelProviderName } from "../types"; +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { validateEnv, validateCharacterConfig } from '../environment'; +import { Clients, ModelProviderName } from '../types'; -describe("Environment Configuration", () => { +describe('Environment Configuration', () => { const originalEnv = process.env; beforeEach(() => { process.env = { ...originalEnv, - OPENAI_API_KEY: "sk-test123", - REDPILL_API_KEY: "test-key", - GROK_API_KEY: "test-key", - GROQ_API_KEY: "gsk_test123", - OPENROUTER_API_KEY: "test-key", - GOOGLE_GENERATIVE_AI_API_KEY: "test-key", - ELEVENLABS_XI_API_KEY: "test-key", + OPENAI_API_KEY: 'sk-test123', + REDPILL_API_KEY: 'test-key', + GROK_API_KEY: 'test-key', + GROQ_API_KEY: 'gsk_test123', + OPENROUTER_API_KEY: 'test-key', + GOOGLE_GENERATIVE_AI_API_KEY: 'test-key', + ELEVENLABS_XI_API_KEY: 'test-key', }; }); @@ -22,183 +22,161 @@ describe("Environment Configuration", () => { process.env = originalEnv; }); - it("should validate correct environment variables", () => { + it('should validate correct environment variables', () => { expect(() => validateEnv()).not.toThrow(); }); - it("should throw error for invalid OpenAI API key format", () => { - process.env.OPENAI_API_KEY = "invalid-key"; - expect(() => validateEnv()).toThrow( - "OpenAI API key must start with 'sk-'" - ); + it('should throw error for invalid OpenAI API key format', () => { + process.env.OPENAI_API_KEY = 'invalid-key'; + expect(() => validateEnv()).toThrow("OpenAI API key must start with 'sk-'"); }); - it("should throw error for invalid GROQ API key format", () => { - process.env.GROQ_API_KEY = "invalid-key"; - expect(() => validateEnv()).toThrow( - "GROQ API key must start with 'gsk_'" - ); + it('should throw error for invalid GROQ API key format', () => { + process.env.GROQ_API_KEY = 'invalid-key'; + expect(() => validateEnv()).toThrow("GROQ API key must start with 'gsk_'"); }); - it("should throw error for missing required keys", () => { + it('should throw error for missing required keys', () => { delete process.env.REDPILL_API_KEY; - expect(() => validateEnv()).toThrow("REDPILL_API_KEY: Required"); + expect(() => validateEnv()).toThrow('REDPILL_API_KEY: Required'); }); - it("should throw error for multiple missing required keys", () => { + it('should throw error for multiple missing required keys', () => { delete process.env.REDPILL_API_KEY; delete process.env.GROK_API_KEY; delete process.env.OPENROUTER_API_KEY; expect(() => validateEnv()).toThrow( - "Environment validation failed:\n" + - "REDPILL_API_KEY: Required\n" + - "GROK_API_KEY: Required\n" + - "OPENROUTER_API_KEY: Required" + 'Environment validation failed:\n' + + 'REDPILL_API_KEY: Required\n' + + 'GROK_API_KEY: Required\n' + + 'OPENROUTER_API_KEY: Required' ); }); }); -describe("Character Configuration", () => { +describe('Character Configuration', () => { const validCharacterConfig = { - name: "Test Character", + name: 'Test Character', modelProvider: ModelProviderName.OPENAI, - bio: "Test bio", - lore: ["Test lore"], - messageExamples: [ - [ - { - user: "user1", - content: { - text: "Hello", - }, - }, - ], - ], - postExamples: ["Test post"], - topics: ["topic1"], - adjectives: ["friendly"], + bio: 'Test bio', + lore: ['Test lore'], + messageExamples: [[ + { + user: 'user1', + content: { + text: 'Hello', + } + } + ]], + postExamples: ['Test post'], + topics: ['topic1'], + adjectives: ['friendly'], clients: [Clients.DISCORD], - plugins: ["test-plugin"], + plugins: ['test-plugin'], style: { - all: ["style1"], - chat: ["chat-style"], - post: ["post-style"], - }, + all: ['style1'], + chat: ['chat-style'], + post: ['post-style'] + } }; - it("should validate correct character configuration", () => { - expect(() => - validateCharacterConfig(validCharacterConfig) - ).not.toThrow(); + it('should validate correct character configuration', () => { + expect(() => validateCharacterConfig(validCharacterConfig)).not.toThrow(); }); - it("should validate configuration with optional fields", () => { + it('should validate configuration with optional fields', () => { const configWithOptionals = { ...validCharacterConfig, - id: "123e4567-e89b-12d3-a456-426614174000", - system: "Test system", + id: '123e4567-e89b-12d3-a456-426614174000', + system: 'Test system', templates: { - greeting: "Hello!", + greeting: 'Hello!' }, - knowledge: ["fact1"], + knowledge: ['fact1'], settings: { secrets: { - key: "value", + key: 'value' }, voice: { - model: "test-model", - url: "http://example.com", - }, - }, + model: 'test-model', + url: 'http://example.com' + } + } }; - expect(() => - validateCharacterConfig(configWithOptionals) - ).not.toThrow(); + expect(() => validateCharacterConfig(configWithOptionals)).not.toThrow(); }); - it("should throw error for missing required fields", () => { + it('should throw error for missing required fields', () => { const invalidConfig = { ...validCharacterConfig }; delete (invalidConfig as any).name; expect(() => validateCharacterConfig(invalidConfig)).toThrow(); }); - it("should validate plugin objects in plugins array", () => { + it('should validate plugin objects in plugins array', () => { const configWithPluginObjects = { ...validCharacterConfig, - plugins: [ - { - name: "test-plugin", - description: "Test description", - }, - ], + plugins: [{ + name: 'test-plugin', + description: 'Test description' + }] }; - expect(() => - validateCharacterConfig(configWithPluginObjects) - ).not.toThrow(); + expect(() => validateCharacterConfig(configWithPluginObjects)).not.toThrow(); }); - it("should validate client-specific configurations", () => { + it('should validate client-specific configurations', () => { const configWithClientConfig = { ...validCharacterConfig, clientConfig: { discord: { shouldIgnoreBotMessages: true, - shouldIgnoreDirectMessages: false, + shouldIgnoreDirectMessages: false }, telegram: { shouldIgnoreBotMessages: true, - shouldIgnoreDirectMessages: true, - }, - }, + shouldIgnoreDirectMessages: true + } + } }; - expect(() => - validateCharacterConfig(configWithClientConfig) - ).not.toThrow(); + expect(() => validateCharacterConfig(configWithClientConfig)).not.toThrow(); }); - it("should validate twitter profile configuration", () => { + it('should validate twitter profile configuration', () => { const configWithTwitter = { ...validCharacterConfig, twitterProfile: { - username: "testuser", - screenName: "Test User", - bio: "Test bio", - nicknames: ["test"], - }, + username: 'testuser', + screenName: 'Test User', + bio: 'Test bio', + nicknames: ['test'] + } }; expect(() => validateCharacterConfig(configWithTwitter)).not.toThrow(); }); - it("should validate model endpoint override", () => { + it('should validate model endpoint override', () => { const configWithEndpoint = { ...validCharacterConfig, - modelEndpointOverride: "custom-endpoint", + modelEndpointOverride: 'custom-endpoint' }; expect(() => validateCharacterConfig(configWithEndpoint)).not.toThrow(); }); - it("should validate message examples with additional properties", () => { + it('should validate message examples with additional properties', () => { const configWithComplexMessage = { ...validCharacterConfig, - messageExamples: [ - [ - { - user: "user1", - content: { - text: "Hello", - action: "wave", - source: "chat", - url: "http://example.com", - inReplyTo: "123e4567-e89b-12d3-a456-426614174000", - attachments: ["file1"], - customField: "value", - }, - }, - ], - ], + messageExamples: [[{ + user: 'user1', + content: { + text: 'Hello', + action: 'wave', + source: 'chat', + url: 'http://example.com', + inReplyTo: '123e4567-e89b-12d3-a456-426614174000', + attachments: ['file1'], + customField: 'value' + } + }]] }; - expect(() => - validateCharacterConfig(configWithComplexMessage) - ).not.toThrow(); + expect(() => validateCharacterConfig(configWithComplexMessage)).not.toThrow(); }); }); diff --git a/packages/core/src/tests/knowledge.test.ts b/packages/core/src/tests/knowledge.test.ts index c637ff4729f..436954f975b 100644 --- a/packages/core/src/tests/knowledge.test.ts +++ b/packages/core/src/tests/knowledge.test.ts @@ -1,126 +1,113 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; -import knowledge from "../knowledge"; -import { AgentRuntime } from "../runtime"; -import { KnowledgeItem, Memory } from "../types"; -import { getEmbeddingZeroVector } from "../embedding"; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import knowledge from '../knowledge'; +import { AgentRuntime } from '../runtime'; +import { KnowledgeItem, Memory } from '../types'; +import { getEmbeddingZeroVector } from '../embedding'; // Mock dependencies -vi.mock("../embedding", () => ({ +vi.mock('../embedding', () => ({ embed: vi.fn().mockResolvedValue(new Float32Array(1536).fill(0)), - getEmbeddingZeroVector: vi - .fn() - .mockReturnValue(new Float32Array(1536).fill(0)), + getEmbeddingZeroVector: vi.fn().mockReturnValue(new Float32Array(1536).fill(0)) })); -vi.mock("../generation", () => ({ - splitChunks: vi.fn().mockImplementation(async (text) => [text]), +vi.mock('../generation', () => ({ + splitChunks: vi.fn().mockImplementation(async (text) => [text]) })); -vi.mock("../uuid", () => ({ - stringToUuid: vi.fn().mockImplementation((str) => str), +vi.mock('../uuid', () => ({ + stringToUuid: vi.fn().mockImplementation((str) => str) })); -describe("Knowledge Module", () => { - describe("preprocess", () => { - it("should handle invalid inputs", () => { - expect(knowledge.preprocess(null)).toBe(""); - expect(knowledge.preprocess(undefined)).toBe(""); - expect(knowledge.preprocess("")).toBe(""); +describe('Knowledge Module', () => { + describe('preprocess', () => { + it('should handle invalid inputs', () => { + expect(knowledge.preprocess(null)).toBe(''); + expect(knowledge.preprocess(undefined)).toBe(''); + expect(knowledge.preprocess('')).toBe(''); }); - it("should remove code blocks and inline code", () => { - const input = - "Here is some code: ```const x = 1;``` and `inline code`"; - expect(knowledge.preprocess(input)).toBe("here is some code: and"); + it('should remove code blocks and inline code', () => { + const input = 'Here is some code: ```const x = 1;``` and `inline code`'; + expect(knowledge.preprocess(input)).toBe('here is some code: and'); }); - it("should handle markdown formatting", () => { - const input = - "# Header\n## Subheader\n[Link](http://example.com)\n![Image](image.jpg)"; - expect(knowledge.preprocess(input)).toBe( - "header subheader link image" - ); + it('should handle markdown formatting', () => { + const input = '# Header\n## Subheader\n[Link](http://example.com)\n![Image](image.jpg)'; + expect(knowledge.preprocess(input)).toBe('header subheader link image'); }); - it("should simplify URLs", () => { - const input = "Visit https://www.example.com/path?param=value"; - expect(knowledge.preprocess(input)).toBe( - "visit example.com/path?param=value" - ); + it('should simplify URLs', () => { + const input = 'Visit https://www.example.com/path?param=value'; + expect(knowledge.preprocess(input)).toBe('visit example.com/path?param=value'); }); - it("should remove Discord mentions and HTML tags", () => { - const input = "Hello <@123456789> and
HTML content
"; - expect(knowledge.preprocess(input)).toBe("hello and html content"); + it('should remove Discord mentions and HTML tags', () => { + const input = 'Hello <@123456789> and
HTML content
'; + expect(knowledge.preprocess(input)).toBe('hello and html content'); }); - it("should normalize whitespace and newlines", () => { - const input = "Multiple spaces\n\n\nand\nnewlines"; - expect(knowledge.preprocess(input)).toBe( - "multiple spaces and newlines" - ); + it('should normalize whitespace and newlines', () => { + const input = 'Multiple spaces\n\n\nand\nnewlines'; + expect(knowledge.preprocess(input)).toBe('multiple spaces and newlines'); }); - it("should remove comments", () => { - const input = "/* Block comment */ Normal text // Line comment"; - expect(knowledge.preprocess(input)).toBe("normal text"); + it('should remove comments', () => { + const input = '/* Block comment */ Normal text // Line comment'; + expect(knowledge.preprocess(input)).toBe('normal text'); }); }); - describe("get and set", () => { + describe('get and set', () => { let mockRuntime: AgentRuntime; beforeEach(() => { mockRuntime = { - agentId: "test-agent", + agentId: 'test-agent', knowledgeManager: { searchMemoriesByEmbedding: vi.fn().mockResolvedValue([ { - content: { - text: "test fragment", - source: "source1", - }, - similarity: 0.9, - }, + content: { text: 'test fragment', source: 'source1' }, + similarity: 0.9 + } ]), - createMemory: vi.fn().mockResolvedValue(undefined), + createMemory: vi.fn().mockResolvedValue(undefined) }, documentsManager: { getMemoryById: vi.fn().mockResolvedValue({ - id: "source1", - content: { text: "test document" }, + id: 'source1', + content: { text: 'test document' } }), - createMemory: vi.fn().mockResolvedValue(undefined), - }, + createMemory: vi.fn().mockResolvedValue(undefined) + } } as unknown as AgentRuntime; }); - describe("get", () => { - it("should handle invalid messages", async () => { + describe('get', () => { + it('should handle invalid messages', async () => { const invalidMessage = {} as Memory; const result = await knowledge.get(mockRuntime, invalidMessage); expect(result).toEqual([]); }); - it("should retrieve knowledge items based on message content", async () => { + it('should retrieve knowledge items based on message content', async () => { const message: Memory = { - agentId: "test-agent", - content: { text: "test query" }, + agentId: 'test-agent', + content: { text: 'test query' } } as unknown as Memory; const result = await knowledge.get(mockRuntime, message); expect(result).toHaveLength(1); expect(result[0]).toEqual({ - id: "source1", - content: { text: "test document" }, + id: 'source1', + content: { text: 'test document' } }); }); - it("should handle empty processed text", async () => { + it('should handle empty processed text', async () => { const message: Memory = { - agentId: "test-agent", - content: { text: "```code only```" }, + agentId: 'test-agent', + content: { text: '```code only```' } } as unknown as Memory; const result = await knowledge.get(mockRuntime, message); @@ -128,52 +115,46 @@ describe("Knowledge Module", () => { }); }); - describe("set", () => { - it("should store knowledge item and its fragments", async () => { + describe('set', () => { + it('should store knowledge item and its fragments', async () => { const item: KnowledgeItem = { - id: "test-id-1234-5678-9101-112131415161", - content: { text: "test content" }, + id: 'test-id-1234-5678-9101-112131415161', + content: { text: 'test content' } }; await knowledge.set(mockRuntime, item); // Check if document was created - expect( - mockRuntime.documentsManager.createMemory - ).toHaveBeenCalledWith( + expect(mockRuntime.documentsManager.createMemory).toHaveBeenCalledWith( expect.objectContaining({ id: item.id, content: item.content, - embedding: getEmbeddingZeroVector(), + embedding: getEmbeddingZeroVector() }) ); // Check if fragment was created - expect( - mockRuntime.knowledgeManager.createMemory - ).toHaveBeenCalledWith( + expect(mockRuntime.knowledgeManager.createMemory).toHaveBeenCalledWith( expect.objectContaining({ content: { source: item.id, - text: expect.any(String), + text: expect.any(String) }, - embedding: expect.any(Float32Array), + embedding: expect.any(Float32Array) }) ); }); - it("should use default chunk size and bleed", async () => { + it('should use default chunk size and bleed', async () => { const item: KnowledgeItem = { - id: "test-id-1234-5678-9101-112131415161", - content: { text: "test content" }, + id: 'test-id-1234-5678-9101-112131415161', + content: { text: 'test content' } }; await knowledge.set(mockRuntime, item); // Verify default parameters were used - expect( - mockRuntime.knowledgeManager.createMemory - ).toHaveBeenCalledTimes(1); + expect(mockRuntime.knowledgeManager.createMemory).toHaveBeenCalledTimes(1); }); }); }); diff --git a/packages/core/src/tests/messages.test.ts b/packages/core/src/tests/messages.test.ts index 66ff029ba67..bbebe103a6d 100644 --- a/packages/core/src/tests/messages.test.ts +++ b/packages/core/src/tests/messages.test.ts @@ -122,7 +122,9 @@ describe("Messages Library", () => { // Assertions expect(formattedMessages).toContain("Check this attachment"); - expect(formattedMessages).toContain("Attachments: ["); + expect(formattedMessages).toContain( + "Attachments: [" + ); }); test("formatMessages should handle empty attachments gracefully", () => { @@ -146,28 +148,28 @@ describe("Messages Library", () => { }); }); -describe("Messages", () => { +describe('Messages', () => { const mockActors: Actor[] = [ { id: "123e4567-e89b-12d3-a456-426614174006" as UUID, - name: "Alice", - username: "alice", + name: 'Alice', + username: 'alice', details: { - tagline: "Software Engineer", - summary: "Full-stack developer with 5 years experience", - quote: "", - }, + tagline: 'Software Engineer', + summary: 'Full-stack developer with 5 years experience', + quote: "" + } }, { id: "123e4567-e89b-12d3-a456-426614174007" as UUID, - name: "Bob", - username: "bob", + name: 'Bob', + username: 'bob', details: { - tagline: "Product Manager", - summary: "Experienced in agile methodologies", - quote: "", - }, - }, + tagline: 'Product Manager', + summary: 'Experienced in agile methodologies', + quote: "" + } + } ]; const mockMessages: Memory[] = [ @@ -177,10 +179,10 @@ describe("Messages", () => { userId: mockActors[0].id, createdAt: Date.now() - 5000, // 5 seconds ago content: { - text: "Hello everyone!", - action: "wave", + text: 'Hello everyone!', + action: 'wave' } as Content, - agentId: "123e4567-e89b-12d3-a456-426614174001", + agentId: "123e4567-e89b-12d3-a456-426614174001" }, { id: "123e4567-e89b-12d3-a456-426614174010" as UUID, @@ -188,171 +190,144 @@ describe("Messages", () => { userId: mockActors[1].id, createdAt: Date.now() - 60000, // 1 minute ago content: { - text: "Hi Alice!", + text: 'Hi Alice!', attachments: [ { id: "123e4567-e89b-12d3-a456-426614174011" as UUID, - title: "Document", - url: "https://example.com/doc.pdf", - }, - ], + title: 'Document', + url: 'https://example.com/doc.pdf' + } + ] } as Content, - agentId: "123e4567-e89b-12d3-a456-426614174001", - }, + agentId: "123e4567-e89b-12d3-a456-426614174001" + } ]; - describe("getActorDetails", () => { - it("should retrieve actor details from database", async () => { + describe('getActorDetails', () => { + it('should retrieve actor details from database', async () => { const mockRuntime = { databaseAdapter: { - getParticipantsForRoom: vi - .fn() - .mockResolvedValue([ - mockActors[0].id, - mockActors[1].id, - ]), + getParticipantsForRoom: vi.fn().mockResolvedValue([mockActors[0].id, mockActors[1].id]), getAccountById: vi.fn().mockImplementation((id) => { - const actor = mockActors.find((a) => a.id === id); + const actor = mockActors.find(a => a.id === id); return Promise.resolve(actor); - }), - }, + }) + } }; const actors = await getActorDetails({ runtime: mockRuntime as any, - roomId: "123e4567-e89b-12d3-a456-426614174009" as UUID, + roomId: "123e4567-e89b-12d3-a456-426614174009" as UUID }); expect(actors).toHaveLength(2); - expect(actors[0].name).toBe("Alice"); - expect(actors[1].name).toBe("Bob"); - expect( - mockRuntime.databaseAdapter.getParticipantsForRoom - ).toHaveBeenCalled(); + expect(actors[0].name).toBe('Alice'); + expect(actors[1].name).toBe('Bob'); + expect(mockRuntime.databaseAdapter.getParticipantsForRoom).toHaveBeenCalled(); }); - it("should filter out null actors", async () => { + it('should filter out null actors', async () => { const invalidId = "123e4567-e89b-12d3-a456-426614174012" as UUID; const mockRuntime = { databaseAdapter: { - getParticipantsForRoom: vi - .fn() - .mockResolvedValue([mockActors[0].id, invalidId]), + getParticipantsForRoom: vi.fn().mockResolvedValue([mockActors[0].id, invalidId]), getAccountById: vi.fn().mockImplementation((id) => { - const actor = mockActors.find((a) => a.id === id); + const actor = mockActors.find(a => a.id === id); return Promise.resolve(actor || null); - }), - }, + }) + } }; const actors = await getActorDetails({ runtime: mockRuntime as any, - roomId: "123e4567-e89b-12d3-a456-426614174009" as UUID, + roomId: "123e4567-e89b-12d3-a456-426614174009" as UUID }); expect(actors).toHaveLength(1); - expect(actors[0].name).toBe("Alice"); + expect(actors[0].name).toBe('Alice'); }); }); - describe("formatActors", () => { - it("should format actors with complete details", () => { + describe('formatActors', () => { + it('should format actors with complete details', () => { const formatted = formatActors({ actors: mockActors }); - expect(formatted).toContain("Alice: Software Engineer"); - expect(formatted).toContain( - "Full-stack developer with 5 years experience" - ); - expect(formatted).toContain("Bob: Product Manager"); - expect(formatted).toContain("Experienced in agile methodologies"); + expect(formatted).toContain('Alice: Software Engineer'); + expect(formatted).toContain('Full-stack developer with 5 years experience'); + expect(formatted).toContain('Bob: Product Manager'); + expect(formatted).toContain('Experienced in agile methodologies'); }); - it("should handle actors without details", () => { + it('should handle actors without details', () => { const actorsWithoutDetails: Actor[] = [ { id: "123e4567-e89b-12d3-a456-426614174013" as UUID, - name: "Charlie", - username: "charlie", + name: 'Charlie', + username: 'charlie', details: { tagline: "Tag", summary: "Summary", - quote: "Quote", - }, - }, + quote: "Quote" + } + } ]; const formatted = formatActors({ actors: actorsWithoutDetails }); - expect(formatted).toBe("Charlie: Tag\nSummary"); + expect(formatted).toBe('Charlie: Tag\nSummary'); }); - it("should handle empty actors array", () => { + it('should handle empty actors array', () => { const formatted = formatActors({ actors: [] }); - expect(formatted).toBe(""); + expect(formatted).toBe(''); }); }); - describe("formatMessages", () => { - it("should format messages with all details", () => { - const formatted = formatMessages({ - messages: mockMessages, - actors: mockActors, - }); - const lines = formatted.split("\n"); + describe('formatMessages', () => { + it('should format messages with all details', () => { + const formatted = formatMessages({ messages: mockMessages, actors: mockActors }); + const lines = formatted.split('\n'); expect(lines[1]).toContain("Alice"); expect(lines[1]).toContain("(wave)"); expect(lines[1]).toContain("(just now)"); }); - it("should handle messages from unknown users", () => { - const messagesWithUnknownUser: Memory[] = [ - { - id: "123e4567-e89b-12d3-a456-426614174014" as UUID, - roomId: "123e4567-e89b-12d3-a456-426614174009" as UUID, - userId: "123e4567-e89b-12d3-a456-426614174015" as UUID, - createdAt: Date.now(), - content: { text: "Test message" } as Content, - agentId: "123e4567-e89b-12d3-a456-426614174001", - }, - ]; - - const formatted = formatMessages({ - messages: messagesWithUnknownUser, - actors: mockActors, - }); - expect(formatted).toContain("Unknown User: Test message"); + it('should handle messages from unknown users', () => { + const messagesWithUnknownUser: Memory[] = [{ + id: "123e4567-e89b-12d3-a456-426614174014" as UUID, + roomId: "123e4567-e89b-12d3-a456-426614174009" as UUID, + userId: "123e4567-e89b-12d3-a456-426614174015" as UUID, + createdAt: Date.now(), + content: { text: 'Test message' } as Content, + agentId: "123e4567-e89b-12d3-a456-426614174001" + }]; + + const formatted = formatMessages({ messages: messagesWithUnknownUser, actors: mockActors }); + expect(formatted).toContain('Unknown User: Test message'); }); - it("should handle messages with no action", () => { - const messagesWithoutAction: Memory[] = [ - { - id: "123e4567-e89b-12d3-a456-426614174016" as UUID, - roomId: "123e4567-e89b-12d3-a456-426614174009" as UUID, - userId: mockActors[0].id, - createdAt: Date.now(), - content: { text: "Simple message" } as Content, - agentId: "123e4567-e89b-12d3-a456-426614174001", - }, - ]; - - const formatted = formatMessages({ - messages: messagesWithoutAction, - actors: mockActors, - }); - expect(formatted).not.toContain("()"); - expect(formatted).toContain("Simple message"); + it('should handle messages with no action', () => { + const messagesWithoutAction: Memory[] = [{ + id: "123e4567-e89b-12d3-a456-426614174016" as UUID, + roomId: "123e4567-e89b-12d3-a456-426614174009" as UUID, + userId: mockActors[0].id, + createdAt: Date.now(), + content: { text: 'Simple message' } as Content, + agentId: "123e4567-e89b-12d3-a456-426614174001" + }]; + + const formatted = formatMessages({ messages: messagesWithoutAction, actors: mockActors }); + expect(formatted).not.toContain('()'); + expect(formatted).toContain('Simple message'); }); - it("should handle empty messages array", () => { - const formatted = formatMessages({ - messages: [], - actors: mockActors, - }); - expect(formatted).toBe(""); + it('should handle empty messages array', () => { + const formatted = formatMessages({ messages: [], actors: mockActors }); + expect(formatted).toBe(''); }); }); - describe("formatTimestamp", () => { - it("should handle exact time boundaries", () => { + describe('formatTimestamp', () => { + it('should handle exact time boundaries', () => { const now = Date.now(); - expect(formatTimestamp(now)).toContain("just now"); + expect(formatTimestamp(now)).toContain('just now'); }); }); }); diff --git a/packages/core/src/tests/models.test.ts b/packages/core/src/tests/models.test.ts index 90ca87dba76..f336093cfdd 100644 --- a/packages/core/src/tests/models.test.ts +++ b/packages/core/src/tests/models.test.ts @@ -26,9 +26,7 @@ vi.mock("../settings", () => { describe("Model Provider Configuration", () => { describe("OpenAI Provider", () => { test("should have correct endpoint", () => { - expect(models[ModelProviderName.OPENAI].endpoint).toBe( - "https://api.openai.com/v1" - ); + expect(models[ModelProviderName.OPENAI].endpoint).toBe("https://api.openai.com/v1"); }); test("should have correct model mappings", () => { @@ -36,9 +34,7 @@ describe("Model Provider Configuration", () => { expect(openAIModels[ModelClass.SMALL]).toBe("gpt-4o-mini"); expect(openAIModels[ModelClass.MEDIUM]).toBe("gpt-4o"); expect(openAIModels[ModelClass.LARGE]).toBe("gpt-4o"); - expect(openAIModels[ModelClass.EMBEDDING]).toBe( - "text-embedding-3-small" - ); + expect(openAIModels[ModelClass.EMBEDDING]).toBe("text-embedding-3-small"); expect(openAIModels[ModelClass.IMAGE]).toBe("dall-e-3"); }); @@ -54,22 +50,14 @@ describe("Model Provider Configuration", () => { describe("Anthropic Provider", () => { test("should have correct endpoint", () => { - expect(models[ModelProviderName.ANTHROPIC].endpoint).toBe( - "https://api.anthropic.com/v1" - ); + expect(models[ModelProviderName.ANTHROPIC].endpoint).toBe("https://api.anthropic.com/v1"); }); test("should have correct model mappings", () => { const anthropicModels = models[ModelProviderName.ANTHROPIC].model; - expect(anthropicModels[ModelClass.SMALL]).toBe( - "claude-3-haiku-20240307" - ); - expect(anthropicModels[ModelClass.MEDIUM]).toBe( - "claude-3-5-sonnet-20241022" - ); - expect(anthropicModels[ModelClass.LARGE]).toBe( - "claude-3-5-sonnet-20241022" - ); + expect(anthropicModels[ModelClass.SMALL]).toBe("claude-3-haiku-20240307"); + expect(anthropicModels[ModelClass.MEDIUM]).toBe("claude-3-5-sonnet-20241022"); + expect(anthropicModels[ModelClass.LARGE]).toBe("claude-3-5-sonnet-20241022"); }); test("should have correct settings configuration", () => { @@ -84,28 +72,16 @@ describe("Model Provider Configuration", () => { describe("LlamaCloud Provider", () => { test("should have correct endpoint", () => { - expect(models[ModelProviderName.LLAMACLOUD].endpoint).toBe( - "https://api.llamacloud.com/v1" - ); + expect(models[ModelProviderName.LLAMACLOUD].endpoint).toBe("https://api.llamacloud.com/v1"); }); test("should have correct model mappings", () => { const llamaCloudModels = models[ModelProviderName.LLAMACLOUD].model; - expect(llamaCloudModels[ModelClass.SMALL]).toBe( - "meta-llama/Llama-3.2-3B-Instruct-Turbo" - ); - expect(llamaCloudModels[ModelClass.MEDIUM]).toBe( - "meta-llama-3.1-8b-instruct" - ); - expect(llamaCloudModels[ModelClass.LARGE]).toBe( - "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo" - ); - expect(llamaCloudModels[ModelClass.EMBEDDING]).toBe( - "togethercomputer/m2-bert-80M-32k-retrieval" - ); - expect(llamaCloudModels[ModelClass.IMAGE]).toBe( - "black-forest-labs/FLUX.1-schnell" - ); + expect(llamaCloudModels[ModelClass.SMALL]).toBe("meta-llama/Llama-3.2-3B-Instruct-Turbo"); + expect(llamaCloudModels[ModelClass.MEDIUM]).toBe("meta-llama-3.1-8b-instruct"); + expect(llamaCloudModels[ModelClass.LARGE]).toBe("meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo"); + expect(llamaCloudModels[ModelClass.EMBEDDING]).toBe("togethercomputer/m2-bert-80M-32k-retrieval"); + expect(llamaCloudModels[ModelClass.IMAGE]).toBe("black-forest-labs/FLUX.1-schnell"); }); test("should have correct settings configuration", () => { @@ -120,11 +96,9 @@ describe("Model Provider Configuration", () => { describe("Google Provider", () => { test("should have correct model mappings", () => { const googleModels = models[ModelProviderName.GOOGLE].model; - expect(googleModels[ModelClass.SMALL]).toBe("gemini-2.0-flash-exp"); - expect(googleModels[ModelClass.MEDIUM]).toBe( - "gemini-2.0-flash-exp" - ); - expect(googleModels[ModelClass.LARGE]).toBe("gemini-2.0-flash-exp"); + expect(googleModels[ModelClass.SMALL]).toBe("gemini-1.5-flash-latest"); + expect(googleModels[ModelClass.MEDIUM]).toBe("gemini-1.5-flash-latest"); + expect(googleModels[ModelClass.LARGE]).toBe("gemini-1.5-pro-latest"); }); }); }); @@ -132,50 +106,28 @@ describe("Model Provider Configuration", () => { describe("Model Retrieval Functions", () => { describe("getModel function", () => { test("should retrieve correct models for different providers and classes", () => { - expect(getModel(ModelProviderName.OPENAI, ModelClass.SMALL)).toBe( - "gpt-4o-mini" - ); - expect( - getModel(ModelProviderName.ANTHROPIC, ModelClass.LARGE) - ).toBe("claude-3-5-sonnet-20241022"); - expect( - getModel(ModelProviderName.LLAMACLOUD, ModelClass.MEDIUM) - ).toBe("meta-llama-3.1-8b-instruct"); + expect(getModel(ModelProviderName.OPENAI, ModelClass.SMALL)).toBe("gpt-4o-mini"); + expect(getModel(ModelProviderName.ANTHROPIC, ModelClass.LARGE)).toBe("claude-3-5-sonnet-20241022"); + expect(getModel(ModelProviderName.LLAMACLOUD, ModelClass.MEDIUM)).toBe("meta-llama-3.1-8b-instruct"); }); test("should handle environment variable overrides", () => { - expect( - getModel(ModelProviderName.OPENROUTER, ModelClass.SMALL) - ).toBe("mock-small-model"); - expect( - getModel(ModelProviderName.OPENROUTER, ModelClass.LARGE) - ).toBe("mock-large-model"); - expect( - getModel(ModelProviderName.ETERNALAI, ModelClass.SMALL) - ).toBe("mock-eternal-model"); + expect(getModel(ModelProviderName.OPENROUTER, ModelClass.SMALL)).toBe("mock-small-model"); + expect(getModel(ModelProviderName.OPENROUTER, ModelClass.LARGE)).toBe("mock-large-model"); + expect(getModel(ModelProviderName.ETERNALAI, ModelClass.SMALL)).toBe("mock-eternal-model"); }); test("should throw error for invalid model provider", () => { - expect(() => - getModel("INVALID_PROVIDER" as any, ModelClass.SMALL) - ).toThrow(); + expect(() => getModel("INVALID_PROVIDER" as any, ModelClass.SMALL)).toThrow(); }); }); describe("getEndpoint function", () => { test("should retrieve correct endpoints for different providers", () => { - expect(getEndpoint(ModelProviderName.OPENAI)).toBe( - "https://api.openai.com/v1" - ); - expect(getEndpoint(ModelProviderName.ANTHROPIC)).toBe( - "https://api.anthropic.com/v1" - ); - expect(getEndpoint(ModelProviderName.LLAMACLOUD)).toBe( - "https://api.llamacloud.com/v1" - ); - expect(getEndpoint(ModelProviderName.ETERNALAI)).toBe( - "https://mock.eternal.ai" - ); + expect(getEndpoint(ModelProviderName.OPENAI)).toBe("https://api.openai.com/v1"); + expect(getEndpoint(ModelProviderName.ANTHROPIC)).toBe("https://api.anthropic.com/v1"); + expect(getEndpoint(ModelProviderName.LLAMACLOUD)).toBe("https://api.llamacloud.com/v1"); + expect(getEndpoint(ModelProviderName.ETERNALAI)).toBe("https://mock.eternal.ai"); }); test("should throw error for invalid provider", () => { @@ -186,7 +138,7 @@ describe("Model Retrieval Functions", () => { describe("Model Settings Validation", () => { test("all providers should have required settings", () => { - Object.values(ModelProviderName).forEach((provider) => { + Object.values(ModelProviderName).forEach(provider => { const providerConfig = models[provider]; expect(providerConfig.settings).toBeDefined(); expect(providerConfig.settings.maxInputTokens).toBeGreaterThan(0); @@ -196,7 +148,7 @@ describe("Model Settings Validation", () => { }); test("all providers should have model mappings for basic model classes", () => { - Object.values(ModelProviderName).forEach((provider) => { + Object.values(ModelProviderName).forEach(provider => { const providerConfig = models[provider]; expect(providerConfig.model).toBeDefined(); expect(providerConfig.model[ModelClass.SMALL]).toBeDefined(); @@ -209,21 +161,13 @@ describe("Model Settings Validation", () => { describe("Environment Variable Integration", () => { test("should use environment variables for LlamaCloud models", () => { const llamaConfig = models[ModelProviderName.LLAMACLOUD]; - expect(llamaConfig.model[ModelClass.SMALL]).toBe( - "meta-llama/Llama-3.2-3B-Instruct-Turbo" - ); - expect(llamaConfig.model[ModelClass.LARGE]).toBe( - "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo" - ); + expect(llamaConfig.model[ModelClass.SMALL]).toBe("meta-llama/Llama-3.2-3B-Instruct-Turbo"); + expect(llamaConfig.model[ModelClass.LARGE]).toBe("meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo"); }); test("should use environment variables for Together models", () => { const togetherConfig = models[ModelProviderName.TOGETHER]; - expect(togetherConfig.model[ModelClass.SMALL]).toBe( - "meta-llama/Llama-3.2-3B-Instruct-Turbo" - ); - expect(togetherConfig.model[ModelClass.LARGE]).toBe( - "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo" - ); + expect(togetherConfig.model[ModelClass.SMALL]).toBe("meta-llama/Llama-3.2-3B-Instruct-Turbo"); + expect(togetherConfig.model[ModelClass.LARGE]).toBe("meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo"); }); }); diff --git a/packages/core/src/tests/parsing.test.ts b/packages/core/src/tests/parsing.test.ts index 1f436adaa2a..636f0b00aff 100644 --- a/packages/core/src/tests/parsing.test.ts +++ b/packages/core/src/tests/parsing.test.ts @@ -1,114 +1,94 @@ -import { describe, it, expect } from "vitest"; +import { describe, it, expect } from 'vitest'; import { parseShouldRespondFromText, parseBooleanFromText, parseJsonArrayFromText, parseJSONObjectFromText, -} from "../parsing"; +} from '../parsing'; -describe("Parsing Module", () => { - describe("parseShouldRespondFromText", () => { - it("should parse exact matches", () => { - expect(parseShouldRespondFromText("[RESPOND]")).toBe("RESPOND"); - expect(parseShouldRespondFromText("[IGNORE]")).toBe("IGNORE"); - expect(parseShouldRespondFromText("[STOP]")).toBe("STOP"); +describe('Parsing Module', () => { + describe('parseShouldRespondFromText', () => { + it('should parse exact matches', () => { + expect(parseShouldRespondFromText('[RESPOND]')).toBe('RESPOND'); + expect(parseShouldRespondFromText('[IGNORE]')).toBe('IGNORE'); + expect(parseShouldRespondFromText('[STOP]')).toBe('STOP'); }); - it("should handle case insensitive input", () => { - expect(parseShouldRespondFromText("[respond]")).toBe("RESPOND"); - expect(parseShouldRespondFromText("[ignore]")).toBe("IGNORE"); - expect(parseShouldRespondFromText("[stop]")).toBe("STOP"); + it('should handle case insensitive input', () => { + expect(parseShouldRespondFromText('[respond]')).toBe('RESPOND'); + expect(parseShouldRespondFromText('[ignore]')).toBe('IGNORE'); + expect(parseShouldRespondFromText('[stop]')).toBe('STOP'); }); - it("should handle text containing keywords", () => { - expect( - parseShouldRespondFromText("I think we should RESPOND here") - ).toBe("RESPOND"); - expect( - parseShouldRespondFromText("Better to IGNORE this one") - ).toBe("IGNORE"); - expect(parseShouldRespondFromText("We need to STOP now")).toBe( - "STOP" - ); + it('should handle text containing keywords', () => { + expect(parseShouldRespondFromText('I think we should RESPOND here')).toBe('RESPOND'); + expect(parseShouldRespondFromText('Better to IGNORE this one')).toBe('IGNORE'); + expect(parseShouldRespondFromText('We need to STOP now')).toBe('STOP'); }); - it("should return null for invalid input", () => { - expect(parseShouldRespondFromText("")).toBe(null); - expect(parseShouldRespondFromText("invalid")).toBe(null); - expect(parseShouldRespondFromText("[INVALID]")).toBe(null); + it('should return null for invalid input', () => { + expect(parseShouldRespondFromText('')).toBe(null); + expect(parseShouldRespondFromText('invalid')).toBe(null); + expect(parseShouldRespondFromText('[INVALID]')).toBe(null); }); }); - describe("parseBooleanFromText", () => { - it("should parse exact YES/NO matches", () => { - expect(parseBooleanFromText("YES")).toBe(true); - expect(parseBooleanFromText("NO")).toBe(false); + describe('parseBooleanFromText', () => { + it('should parse exact YES/NO matches', () => { + expect(parseBooleanFromText('YES')).toBe(true); + expect(parseBooleanFromText('NO')).toBe(false); }); - it("should handle case insensitive input", () => { - expect(parseBooleanFromText("yes")).toBe(true); - expect(parseBooleanFromText("no")).toBe(false); + it('should handle case insensitive input', () => { + expect(parseBooleanFromText('yes')).toBe(true); + expect(parseBooleanFromText('no')).toBe(false); }); - it("should return null for invalid input", () => { - expect(parseBooleanFromText("")).toBe(null); - expect(parseBooleanFromText("maybe")).toBe(null); - expect(parseBooleanFromText("YES NO")).toBe(null); + it('should return null for invalid input', () => { + expect(parseBooleanFromText('')).toBe(null); + expect(parseBooleanFromText('maybe')).toBe(null); + expect(parseBooleanFromText('YES NO')).toBe(null); }); }); - describe("parseJsonArrayFromText", () => { - it("should parse JSON array from code block", () => { + describe('parseJsonArrayFromText', () => { + it('should parse JSON array from code block', () => { const input = '```json\n["item1", "item2", "item3"]\n```'; - expect(parseJsonArrayFromText(input)).toEqual([ - "item1", - "item2", - "item3", - ]); + expect(parseJsonArrayFromText(input)).toEqual(['item1', 'item2', 'item3']); }); - it("should handle empty arrays", () => { - expect(parseJsonArrayFromText("```json\n[]\n```")).toEqual([]); - expect(parseJsonArrayFromText("[]")).toEqual(null); + it('should handle empty arrays', () => { + expect(parseJsonArrayFromText('```json\n[]\n```')).toEqual([]); + expect(parseJsonArrayFromText('[]')).toEqual(null); }); - it("should return null for invalid JSON", () => { - expect(parseJsonArrayFromText("invalid")).toBe(null); - expect(parseJsonArrayFromText("[invalid]")).toBe(null); - expect(parseJsonArrayFromText("```json\n[invalid]\n```")).toBe( - null - ); + it('should return null for invalid JSON', () => { + expect(parseJsonArrayFromText('invalid')).toBe(null); + expect(parseJsonArrayFromText('[invalid]')).toBe(null); + expect(parseJsonArrayFromText('```json\n[invalid]\n```')).toBe(null); }); }); - describe("parseJSONObjectFromText", () => { - it("should parse JSON object from code block", () => { + describe('parseJSONObjectFromText', () => { + it('should parse JSON object from code block', () => { const input = '```json\n{"key": "value", "number": 42}\n```'; - expect(parseJSONObjectFromText(input)).toEqual({ - key: "value", - number: 42, - }); + expect(parseJSONObjectFromText(input)).toEqual({ key: 'value', number: 42 }); }); - it("should parse JSON object without code block", () => { + it('should parse JSON object without code block', () => { const input = '{"key": "value", "number": 42}'; - expect(parseJSONObjectFromText(input)).toEqual({ - key: "value", - number: 42, - }); + expect(parseJSONObjectFromText(input)).toEqual({ key: 'value', number: 42 }); }); - it("should handle empty objects", () => { - expect(parseJSONObjectFromText("```json\n{}\n```")).toEqual({}); - expect(parseJSONObjectFromText("{}")).toEqual({}); + it('should handle empty objects', () => { + expect(parseJSONObjectFromText('```json\n{}\n```')).toEqual({}); + expect(parseJSONObjectFromText('{}')).toEqual({}); }); - it("should return null for invalid JSON", () => { - expect(parseJSONObjectFromText("invalid")).toBe(null); - expect(parseJSONObjectFromText("{invalid}")).toBe(null); - expect(parseJSONObjectFromText("```json\n{invalid}\n```")).toBe( - null - ); + it('should return null for invalid JSON', () => { + expect(parseJSONObjectFromText('invalid')).toBe(null); + expect(parseJSONObjectFromText('{invalid}')).toBe(null); + expect(parseJSONObjectFromText('```json\n{invalid}\n```')).toBe(null); }); }); }); diff --git a/packages/core/src/tests/runtime.test.ts b/packages/core/src/tests/runtime.test.ts index ef0a6a571e4..292de6670a0 100644 --- a/packages/core/src/tests/runtime.test.ts +++ b/packages/core/src/tests/runtime.test.ts @@ -47,7 +47,7 @@ const mockDatabaseAdapter: IDatabaseAdapter = { setParticipantUserState: vi.fn().mockResolvedValue(undefined), createRelationship: vi.fn().mockResolvedValue(true), getRelationship: vi.fn().mockResolvedValue(null), - getRelationships: vi.fn().mockResolvedValue([]), + getRelationships: vi.fn().mockResolvedValue([]) }; const mockCacheManager = { @@ -113,11 +113,7 @@ describe("AgentRuntime", () => { userId: "123e4567-e89b-12d3-a456-426614174005", agentId: "123e4567-e89b-12d3-a456-426614174005", roomId: "123e4567-e89b-12d3-a456-426614174003", - content: { - type: "text", - text: "test response", - action: "testAction", - }, + content: { type: "text", text: "test response", action: "testAction" }, }; await runtime.processActions(message, [response], { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index e07cb74e67c..931416c1a0f 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -255,7 +255,6 @@ export enum ModelProviderName { VENICE = "venice", AKASH_CHAT_API = "akash_chat_api", LIVEPEER = "livepeer", - INFERA = "infera", } /** diff --git a/packages/core/tsconfig.build.json b/packages/core/tsconfig.build.json index e3eb058310d..e56b5e155c1 100644 --- a/packages/core/tsconfig.build.json +++ b/packages/core/tsconfig.build.json @@ -1,8 +1,8 @@ { - "extends": "./tsconfig.json", - "compilerOptions": { - "sourceMap": true, - "inlineSources": true, - "sourceRoot": "/" - } -} + "extends": "./tsconfig.json", + "compilerOptions": { + "sourceMap": true, + "inlineSources": true, + "sourceRoot": "/" + } + } diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index cb33a265893..c19e06bb645 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -2,7 +2,10 @@ "compilerOptions": { "target": "ESNext", "module": "ESNext", - "lib": ["ESNext", "dom"], + "lib": [ + "ESNext", + "dom" + ], "moduleResolution": "Bundler", "outDir": "./dist", "rootDir": "./src", @@ -20,8 +23,17 @@ "noEmitOnError": false, "moduleDetection": "force", "allowArbitraryExtensions": true, - "customConditions": ["@elizaos/source"] + "customConditions": [ + "@elizaos/source" + ], }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "src/**/*.d.ts", "types/**/*.test.ts"] + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist", + "src/**/*.d.ts", + "types/**/*.test.ts" + ] } diff --git a/packages/core/types.ts b/packages/core/types.ts deleted file mode 100644 index 8fb9e2814bd..00000000000 --- a/packages/core/types.ts +++ /dev/null @@ -1,1332 +0,0 @@ -import { Readable } from "stream"; - -/** - * Represents a UUID string in the format "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - */ -export type UUID = `${string}-${string}-${string}-${string}-${string}`; - -/** - * Represents the content of a message or communication - */ -export interface Content { - /** The main text content */ - text: string; - - /** Optional action associated with the message */ - action?: string; - - /** Optional source/origin of the content */ - source?: string; - - /** URL of the original message/post (e.g. tweet URL, Discord message link) */ - url?: string; - - /** UUID of parent message if this is a reply/thread */ - inReplyTo?: UUID; - - /** Array of media attachments */ - attachments?: Media[]; - - /** Additional dynamic properties */ - [key: string]: unknown; -} - -/** - * Example content with associated user for demonstration purposes - */ -export interface ActionExample { - /** User associated with the example */ - user: string; - - /** Content of the example */ - content: Content; -} - -/** - * Example conversation content with user ID - */ -export interface ConversationExample { - /** UUID of user in conversation */ - userId: UUID; - - /** Content of the conversation */ - content: Content; -} - -/** - * Represents an actor/participant in a conversation - */ -export interface Actor { - /** Display name */ - name: string; - - /** Username/handle */ - username: string; - - /** Additional profile details */ - details: { - /** Short profile tagline */ - tagline: string; - - /** Longer profile summary */ - summary: string; - - /** Favorite quote */ - quote: string; - }; - - /** Unique identifier */ - id: UUID; -} - -/** - * Represents a single objective within a goal - */ -export interface Objective { - /** Optional unique identifier */ - id?: string; - - /** Description of what needs to be achieved */ - description: string; - - /** Whether objective is completed */ - completed: boolean; -} - -/** - * Status enum for goals - */ -export enum GoalStatus { - DONE = "DONE", - FAILED = "FAILED", - IN_PROGRESS = "IN_PROGRESS", -} - -/** - * Represents a high-level goal composed of objectives - */ -export interface Goal { - /** Optional unique identifier */ - id?: UUID; - - /** Room ID where goal exists */ - roomId: UUID; - - /** User ID of goal owner */ - userId: UUID; - - /** Name/title of the goal */ - name: string; - - /** Current status */ - status: GoalStatus; - - /** Component objectives */ - objectives: Objective[]; -} - -/** - * Model size/type classification - */ -export enum ModelClass { - SMALL = "small", - MEDIUM = "medium", - LARGE = "large", - EMBEDDING = "embedding", - IMAGE = "image", -} - -/** - * Configuration for an AI model - */ -export type Model = { - /** Optional API endpoint */ - endpoint?: string; - - /** Model settings */ - settings: { - /** Maximum input tokens */ - maxInputTokens: number; - - /** Maximum output tokens */ - maxOutputTokens: number; - - /** Optional frequency penalty */ - frequency_penalty?: number; - - /** Optional presence penalty */ - presence_penalty?: number; - - /** Optional repetition penalty */ - repetition_penalty?: number; - - /** Stop sequences */ - stop: string[]; - - /** Temperature setting */ - temperature: number; - - /** Optional telemetry configuration (experimental) */ - experimental_telemetry?: TelemetrySettings; - }; - - /** Optional image generation settings */ - imageSettings?: { - steps?: number; - }; - - /** Model names by size class */ - model: { - [ModelClass.SMALL]: string; - [ModelClass.MEDIUM]: string; - [ModelClass.LARGE]: string; - [ModelClass.EMBEDDING]?: string; - [ModelClass.IMAGE]?: string; - }; -}; - -/** - * Model configurations by provider - */ -export type Models = { - [ModelProviderName.OPENAI]: Model; - [ModelProviderName.ETERNALAI]: Model; - [ModelProviderName.ANTHROPIC]: Model; - [ModelProviderName.GROK]: Model; - [ModelProviderName.GROQ]: Model; - [ModelProviderName.LLAMACLOUD]: Model; - [ModelProviderName.TOGETHER]: Model; - [ModelProviderName.LLAMALOCAL]: Model; - [ModelProviderName.GOOGLE]: Model; - [ModelProviderName.CLAUDE_VERTEX]: Model; - [ModelProviderName.REDPILL]: Model; - [ModelProviderName.OPENROUTER]: Model; - [ModelProviderName.OLLAMA]: Model; - [ModelProviderName.HEURIST]: Model; - [ModelProviderName.GALADRIEL]: Model; - [ModelProviderName.FAL]: Model; - [ModelProviderName.GAIANET]: Model; - [ModelProviderName.ALI_BAILIAN]: Model; - [ModelProviderName.VOLENGINE]: Model; - [ModelProviderName.NANOGPT]: Model; - [ModelProviderName.HYPERBOLIC]: Model; - [ModelProviderName.VENICE]: Model; - [ModelProviderName.AKASH_CHAT_API]: Model; - [ModelProviderName.LIVEPEER]: Model; - [ModelProviderName.INFERA]: Model; -}; - -/** - * Available model providers - */ -export enum ModelProviderName { - OPENAI = "openai", - ETERNALAI = "eternalai", - ANTHROPIC = "anthropic", - GROK = "grok", - GROQ = "groq", - LLAMACLOUD = "llama_cloud", - TOGETHER = "together", - LLAMALOCAL = "llama_local", - GOOGLE = "google", - CLAUDE_VERTEX = "claude_vertex", - REDPILL = "redpill", - OPENROUTER = "openrouter", - OLLAMA = "ollama", - HEURIST = "heurist", - GALADRIEL = "galadriel", - FAL = "falai", - GAIANET = "gaianet", - ALI_BAILIAN = "ali_bailian", - VOLENGINE = "volengine", - NANOGPT = "nanogpt", - HYPERBOLIC = "hyperbolic", - VENICE = "venice", - AKASH_CHAT_API = "akash_chat_api", - LIVEPEER = "livepeer", - INFERA = "infera", -} - -/** - * Represents the current state/context of a conversation - */ -export interface State { - /** ID of user who sent current message */ - userId?: UUID; - - /** ID of agent in conversation */ - agentId?: UUID; - - /** Agent's biography */ - bio: string; - - /** Agent's background lore */ - lore: string; - - /** Message handling directions */ - messageDirections: string; - - /** Post handling directions */ - postDirections: string; - - /** Current room/conversation ID */ - roomId: UUID; - - /** Optional agent name */ - agentName?: string; - - /** Optional message sender name */ - senderName?: string; - - /** String representation of conversation actors */ - actors: string; - - /** Optional array of actor objects */ - actorsData?: Actor[]; - - /** Optional string representation of goals */ - goals?: string; - - /** Optional array of goal objects */ - goalsData?: Goal[]; - - /** Recent message history as string */ - recentMessages: string; - - /** Recent message objects */ - recentMessagesData: Memory[]; - - /** Optional valid action names */ - actionNames?: string; - - /** Optional action descriptions */ - actions?: string; - - /** Optional action objects */ - actionsData?: Action[]; - - /** Optional action examples */ - actionExamples?: string; - - /** Optional provider descriptions */ - providers?: string; - - /** Optional response content */ - responseData?: Content; - - /** Optional recent interaction objects */ - recentInteractionsData?: Memory[]; - - /** Optional recent interactions string */ - recentInteractions?: string; - - /** Optional formatted conversation */ - formattedConversation?: string; - - /** Optional formatted knowledge */ - knowledge?: string; - /** Optional knowledge data */ - knowledgeData?: KnowledgeItem[]; - - /** Additional dynamic properties */ - [key: string]: unknown; -} - -/** - * Represents a stored memory/message - */ -export interface Memory { - /** Optional unique identifier */ - id?: UUID; - - /** Associated user ID */ - userId: UUID; - - /** Associated agent ID */ - agentId: UUID; - - /** Optional creation timestamp */ - createdAt?: number; - - /** Memory content */ - content: Content; - - /** Optional embedding vector */ - embedding?: number[]; - - /** Associated room ID */ - roomId: UUID; - - /** Whether memory is unique */ - unique?: boolean; - - /** Embedding similarity score */ - similarity?: number; -} - -/** - * Example message for demonstration - */ -export interface MessageExample { - /** Associated user */ - user: string; - - /** Message content */ - content: Content; -} - -/** - * Handler function type for processing messages - */ -export type Handler = ( - runtime: IAgentRuntime, - message: Memory, - state?: State, - options?: { [key: string]: unknown }, - callback?: HandlerCallback -) => Promise; - -/** - * Callback function type for handlers - */ -export type HandlerCallback = ( - response: Content, - files?: any -) => Promise; - -/** - * Validator function type for actions/evaluators - */ -export type Validator = ( - runtime: IAgentRuntime, - message: Memory, - state?: State -) => Promise; - -/** - * Represents an action the agent can perform - */ -export interface Action { - /** Similar action descriptions */ - similes: string[]; - - /** Detailed description */ - description: string; - - /** Example usages */ - examples: ActionExample[][]; - - /** Handler function */ - handler: Handler; - - /** Action name */ - name: string; - - /** Validation function */ - validate: Validator; - - /** Whether to suppress the initial message when this action is used */ - suppressInitialMessage?: boolean; -} - -/** - * Example for evaluating agent behavior - */ -export interface EvaluationExample { - /** Evaluation context */ - context: string; - - /** Example messages */ - messages: Array; - - /** Expected outcome */ - outcome: string; -} - -/** - * Evaluator for assessing agent responses - */ -export interface Evaluator { - /** Whether to always run */ - alwaysRun?: boolean; - - /** Detailed description */ - description: string; - - /** Similar evaluator descriptions */ - similes: string[]; - - /** Example evaluations */ - examples: EvaluationExample[]; - - /** Handler function */ - handler: Handler; - - /** Evaluator name */ - name: string; - - /** Validation function */ - validate: Validator; -} - -/** - * Provider for external data/services - */ -export interface Provider { - /** Data retrieval function */ - get: ( - runtime: IAgentRuntime, - message: Memory, - state?: State - ) => Promise; -} - -/** - * Represents a relationship between users - */ -export interface Relationship { - /** Unique identifier */ - id: UUID; - - /** First user ID */ - userA: UUID; - - /** Second user ID */ - userB: UUID; - - /** Primary user ID */ - userId: UUID; - - /** Associated room ID */ - roomId: UUID; - - /** Relationship status */ - status: string; - - /** Optional creation timestamp */ - createdAt?: string; -} - -/** - * Represents a user account - */ -export interface Account { - /** Unique identifier */ - id: UUID; - - /** Display name */ - name: string; - - /** Username */ - username: string; - - /** Optional additional details */ - details?: { [key: string]: any }; - - /** Optional email */ - email?: string; - - /** Optional avatar URL */ - avatarUrl?: string; -} - -/** - * Room participant with account details - */ -export interface Participant { - /** Unique identifier */ - id: UUID; - - /** Associated account */ - account: Account; -} - -/** - * Represents a conversation room - */ -export interface Room { - /** Unique identifier */ - id: UUID; - - /** Room participants */ - participants: Participant[]; -} - -/** - * Represents a media attachment - */ -export type Media = { - /** Unique identifier */ - id: string; - - /** Media URL */ - url: string; - - /** Media title */ - title: string; - - /** Media source */ - source: string; - - /** Media description */ - description: string; - - /** Text content */ - text: string; - - /** Content type */ - contentType?: string; -}; - -/** - * Client interface for platform connections - */ -export type Client = { - /** Start client connection */ - start: (runtime: IAgentRuntime) => Promise; - - /** Stop client connection */ - stop: (runtime: IAgentRuntime) => Promise; -}; - -/** - * Plugin for extending agent functionality - */ -export type Plugin = { - /** Plugin name */ - name: string; - - /** Plugin description */ - description: string; - - /** Optional actions */ - actions?: Action[]; - - /** Optional providers */ - providers?: Provider[]; - - /** Optional evaluators */ - evaluators?: Evaluator[]; - - /** Optional services */ - services?: Service[]; - - /** Optional clients */ - clients?: Client[]; -}; - -/** - * Available client platforms - */ -export enum Clients { - DISCORD = "discord", - DIRECT = "direct", - TWITTER = "twitter", - TELEGRAM = "telegram", - FARCASTER = "farcaster", - LENS = "lens", - AUTO = "auto", - SLACK = "slack", -} - -export interface IAgentConfig { - [key: string]: string; -} - -export type TelemetrySettings = { - /** - * Enable or disable telemetry. Disabled by default while experimental. - */ - isEnabled?: boolean; - /** - * Enable or disable input recording. Enabled by default. - * - * You might want to disable input recording to avoid recording sensitive - * information, to reduce data transfers, or to increase performance. - */ - recordInputs?: boolean; - /** - * Enable or disable output recording. Enabled by default. - * - * You might want to disable output recording to avoid recording sensitive - * information, to reduce data transfers, or to increase performance. - */ - recordOutputs?: boolean; - /** - * Identifier for this function. Used to group telemetry data by function. - */ - functionId?: string; -}; - -export interface ModelConfiguration { - temperature?: number; - max_response_length?: number; - frequency_penalty?: number; - presence_penalty?: number; - maxInputTokens?: number; - experimental_telemetry?: TelemetrySettings; -} - -/** - * Configuration for an agent character - */ -export type Character = { - /** Optional unique identifier */ - id?: UUID; - - /** Character name */ - name: string; - - /** Optional username */ - username?: string; - - /** Optional system prompt */ - system?: string; - - /** Model provider to use */ - modelProvider: ModelProviderName; - - /** Image model provider to use, if different from modelProvider */ - imageModelProvider?: ModelProviderName; - - /** Image Vision model provider to use, if different from modelProvider */ - imageVisionModelProvider?: ModelProviderName; - - /** Optional model endpoint override */ - modelEndpointOverride?: string; - - /** Optional prompt templates */ - templates?: { - goalsTemplate?: string; - factsTemplate?: string; - messageHandlerTemplate?: string; - shouldRespondTemplate?: string; - continueMessageHandlerTemplate?: string; - evaluationTemplate?: string; - twitterSearchTemplate?: string; - twitterActionTemplate?: string; - twitterPostTemplate?: string; - twitterMessageHandlerTemplate?: string; - twitterShouldRespondTemplate?: string; - farcasterPostTemplate?: string; - lensPostTemplate?: string; - farcasterMessageHandlerTemplate?: string; - lensMessageHandlerTemplate?: string; - farcasterShouldRespondTemplate?: string; - lensShouldRespondTemplate?: string; - telegramMessageHandlerTemplate?: string; - telegramShouldRespondTemplate?: string; - discordVoiceHandlerTemplate?: string; - discordShouldRespondTemplate?: string; - discordMessageHandlerTemplate?: string; - slackMessageHandlerTemplate?: string; - slackShouldRespondTemplate?: string; - }; - - /** Character biography */ - bio: string | string[]; - - /** Character background lore */ - lore: string[]; - - /** Example messages */ - messageExamples: MessageExample[][]; - - /** Example posts */ - postExamples: string[]; - - /** Known topics */ - topics: string[]; - - /** Character traits */ - adjectives: string[]; - - /** Optional knowledge base */ - knowledge?: string[]; - - /** Supported client platforms */ - clients: Clients[]; - - /** Available plugins */ - plugins: Plugin[]; - - /** Optional configuration */ - settings?: { - secrets?: { [key: string]: string }; - intiface?: boolean; - imageSettings?: { - steps?: number; - width?: number; - height?: number; - negativePrompt?: string; - numIterations?: number; - guidanceScale?: number; - seed?: number; - modelId?: string; - jobId?: string; - count?: number; - stylePreset?: string; - hideWatermark?: boolean; - }; - voice?: { - model?: string; // For VITS - url?: string; // Legacy VITS support - elevenlabs?: { - // New structured ElevenLabs config - voiceId: string; - model?: string; - stability?: string; - similarityBoost?: string; - style?: string; - useSpeakerBoost?: string; - }; - }; - model?: string; - modelConfig?: ModelConfiguration; - embeddingModel?: string; - chains?: { - evm?: any[]; - solana?: any[]; - [key: string]: any[]; - }; - transcription?: TranscriptionProvider; - }; - - /** Optional client-specific config */ - clientConfig?: { - discord?: { - shouldIgnoreBotMessages?: boolean; - shouldIgnoreDirectMessages?: boolean; - shouldRespondOnlyToMentions?: boolean; - messageSimilarityThreshold?: number; - isPartOfTeam?: boolean; - teamAgentIds?: string[]; - teamLeaderId?: string; - teamMemberInterestKeywords?: string[]; - }; - telegram?: { - shouldIgnoreBotMessages?: boolean; - shouldIgnoreDirectMessages?: boolean; - shouldRespondOnlyToMentions?: boolean; - shouldOnlyJoinInAllowedGroups?: boolean; - allowedGroupIds?: string[]; - messageSimilarityThreshold?: number; - isPartOfTeam?: boolean; - teamAgentIds?: string[]; - teamLeaderId?: string; - teamMemberInterestKeywords?: string[]; - }; - slack?: { - shouldIgnoreBotMessages?: boolean; - shouldIgnoreDirectMessages?: boolean; - }; - gitbook?: { - keywords?: { - projectTerms?: string[]; - generalQueries?: string[]; - }; - documentTriggers?: string[]; - }; - }; - - /** Writing style guides */ - style: { - all: string[]; - chat: string[]; - post: string[]; - }; - - /** Optional Twitter profile */ - twitterProfile?: { - id: string; - username: string; - screenName: string; - bio: string; - nicknames?: string[]; - }; - /** Optional NFT prompt */ - nft?: { - prompt: string; - }; -}; - -/** - * Interface for database operations - */ -export interface IDatabaseAdapter { - /** Database instance */ - db: any; - - /** Optional initialization */ - init(): Promise; - - /** Close database connection */ - close(): Promise; - - /** Get account by ID */ - getAccountById(userId: UUID): Promise; - - /** Create new account */ - createAccount(account: Account): Promise; - - /** Get memories matching criteria */ - getMemories(params: { - roomId: UUID; - count?: number; - unique?: boolean; - tableName: string; - agentId: UUID; - start?: number; - end?: number; - }): Promise; - - getMemoryById(id: UUID): Promise; - - getMemoriesByRoomIds(params: { - tableName: string; - agentId: UUID; - roomIds: UUID[]; - }): Promise; - - getCachedEmbeddings(params: { - query_table_name: string; - query_threshold: number; - query_input: string; - query_field_name: string; - query_field_sub_name: string; - query_match_count: number; - }): Promise<{ embedding: number[]; levenshtein_score: number }[]>; - - log(params: { - body: { [key: string]: unknown }; - userId: UUID; - roomId: UUID; - type: string; - }): Promise; - - getActorDetails(params: { roomId: UUID }): Promise; - - searchMemories(params: { - tableName: string; - agentId: UUID; - roomId: UUID; - embedding: number[]; - match_threshold: number; - match_count: number; - unique: boolean; - }): Promise; - - updateGoalStatus(params: { - goalId: UUID; - status: GoalStatus; - }): Promise; - - searchMemoriesByEmbedding( - embedding: number[], - params: { - match_threshold?: number; - count?: number; - roomId?: UUID; - agentId?: UUID; - unique?: boolean; - tableName: string; - } - ): Promise; - - createMemory( - memory: Memory, - tableName: string, - unique?: boolean - ): Promise; - - removeMemory(memoryId: UUID, tableName: string): Promise; - - removeAllMemories(roomId: UUID, tableName: string): Promise; - - countMemories( - roomId: UUID, - unique?: boolean, - tableName?: string - ): Promise; - - getGoals(params: { - agentId: UUID; - roomId: UUID; - userId?: UUID | null; - onlyInProgress?: boolean; - count?: number; - }): Promise; - - updateGoal(goal: Goal): Promise; - - createGoal(goal: Goal): Promise; - - removeGoal(goalId: UUID): Promise; - - removeAllGoals(roomId: UUID): Promise; - - getRoom(roomId: UUID): Promise; - - createRoom(roomId?: UUID): Promise; - - removeRoom(roomId: UUID): Promise; - - getRoomsForParticipant(userId: UUID): Promise; - - getRoomsForParticipants(userIds: UUID[]): Promise; - - addParticipant(userId: UUID, roomId: UUID): Promise; - - removeParticipant(userId: UUID, roomId: UUID): Promise; - - getParticipantsForAccount(userId: UUID): Promise; - - getParticipantsForRoom(roomId: UUID): Promise; - - getParticipantUserState( - roomId: UUID, - userId: UUID - ): Promise<"FOLLOWED" | "MUTED" | null>; - - setParticipantUserState( - roomId: UUID, - userId: UUID, - state: "FOLLOWED" | "MUTED" | null - ): Promise; - - createRelationship(params: { userA: UUID; userB: UUID }): Promise; - - getRelationship(params: { - userA: UUID; - userB: UUID; - }): Promise; - - getRelationships(params: { userId: UUID }): Promise; -} - -export interface IDatabaseCacheAdapter { - getCache(params: { - agentId: UUID; - key: string; - }): Promise; - - setCache(params: { - agentId: UUID; - key: string; - value: string; - }): Promise; - - deleteCache(params: { agentId: UUID; key: string }): Promise; -} - -export interface IMemoryManager { - runtime: IAgentRuntime; - tableName: string; - constructor: Function; - - addEmbeddingToMemory(memory: Memory): Promise; - - getMemories(opts: { - roomId: UUID; - count?: number; - unique?: boolean; - start?: number; - end?: number; - }): Promise; - - getCachedEmbeddings( - content: string - ): Promise<{ embedding: number[]; levenshtein_score: number }[]>; - - getMemoryById(id: UUID): Promise; - getMemoriesByRoomIds(params: { roomIds: UUID[] }): Promise; - searchMemoriesByEmbedding( - embedding: number[], - opts: { - match_threshold?: number; - count?: number; - roomId: UUID; - unique?: boolean; - } - ): Promise; - - createMemory(memory: Memory, unique?: boolean): Promise; - - removeMemory(memoryId: UUID): Promise; - - removeAllMemories(roomId: UUID): Promise; - - countMemories(roomId: UUID, unique?: boolean): Promise; -} - -export type CacheOptions = { - expires?: number; -}; - -export enum CacheStore { - REDIS = "redis", - DATABASE = "database", - FILESYSTEM = "filesystem", -} - -export interface ICacheManager { - get(key: string): Promise; - set(key: string, value: T, options?: CacheOptions): Promise; - delete(key: string): Promise; -} - -export abstract class Service { - private static instance: Service | null = null; - - static get serviceType(): ServiceType { - throw new Error("Service must implement static serviceType getter"); - } - - public static getInstance(): T { - if (!Service.instance) { - 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 { - // Properties - agentId: UUID; - serverUrl: string; - databaseAdapter: IDatabaseAdapter; - token: string | null; - modelProvider: ModelProviderName; - imageModelProvider: ModelProviderName; - imageVisionModelProvider: ModelProviderName; - character: Character; - providers: Provider[]; - actions: Action[]; - evaluators: Evaluator[]; - plugins: Plugin[]; - - fetch?: typeof fetch | null; - - messageManager: IMemoryManager; - descriptionManager: IMemoryManager; - documentsManager: IMemoryManager; - knowledgeManager: IMemoryManager; - loreManager: IMemoryManager; - - cacheManager: ICacheManager; - - services: Map; - // any could be EventEmitter - // but I think the real solution is forthcoming as a base client interface - clients: Record; - - initialize(): Promise; - - registerMemoryManager(manager: IMemoryManager): void; - - getMemoryManager(name: string): IMemoryManager | null; - - getService(service: ServiceType): T | null; - - registerService(service: Service): void; - - getSetting(key: string): string | null; - - // Methods - getConversationLength(): number; - - processActions( - message: Memory, - responses: Memory[], - state?: State, - callback?: HandlerCallback - ): Promise; - - evaluate( - message: Memory, - state?: State, - didRespond?: boolean, - callback?: HandlerCallback - ): Promise; - - ensureParticipantExists(userId: UUID, roomId: UUID): Promise; - - ensureUserExists( - userId: UUID, - userName: string | null, - name: string | null, - source: string | null - ): Promise; - - registerAction(action: Action): void; - - ensureConnection( - userId: UUID, - roomId: UUID, - userName?: string, - userScreenName?: string, - source?: string - ): Promise; - - ensureParticipantInRoom(userId: UUID, roomId: UUID): Promise; - - ensureRoomExists(roomId: UUID): Promise; - - composeState( - message: Memory, - additionalKeys?: { [key: string]: unknown } - ): Promise; - - updateRecentMessageState(state: State): Promise; -} - -export interface IImageDescriptionService extends Service { - describeImage( - imageUrl: string - ): Promise<{ title: string; description: string }>; -} - -export interface ITranscriptionService extends Service { - transcribeAttachment(audioBuffer: ArrayBuffer): Promise; - transcribeAttachmentLocally( - audioBuffer: ArrayBuffer - ): Promise; - transcribe(audioBuffer: ArrayBuffer): Promise; - transcribeLocally(audioBuffer: ArrayBuffer): Promise; -} - -export interface IVideoService extends Service { - isVideoUrl(url: string): boolean; - fetchVideoInfo(url: string): Promise; - downloadVideo(videoInfo: Media): Promise; - processVideo(url: string, runtime: IAgentRuntime): Promise; -} - -export interface ITextGenerationService extends Service { - initializeModel(): Promise; - queueMessageCompletion( - context: string, - temperature: number, - stop: string[], - frequency_penalty: number, - presence_penalty: number, - max_tokens: number - ): Promise; - queueTextCompletion( - context: string, - temperature: number, - stop: string[], - frequency_penalty: number, - presence_penalty: number, - max_tokens: number - ): Promise; - getEmbeddingResponse(input: string): Promise; -} - -export interface IBrowserService extends Service { - closeBrowser(): Promise; - getPageContent( - url: string, - runtime: IAgentRuntime - ): Promise<{ title: string; description: string; bodyContent: string }>; -} - -export interface ISpeechService extends Service { - getInstance(): ISpeechService; - generate(runtime: IAgentRuntime, text: string): Promise; -} - -export interface IPdfService extends Service { - getInstance(): IPdfService; - convertPdfToText(pdfBuffer: Buffer): Promise; -} - -export interface IAwsS3Service extends Service { - uploadFile( - imagePath: string, - subDirectory: string, - useSignedUrl: boolean, - expiresIn: number - ): Promise<{ - success: boolean; - url?: string; - error?: string; - }>; - generateSignedUrl(fileName: string, expiresIn: number): Promise; -} - -export type SearchImage = { - url: string; - description?: string; -}; - -export type SearchResult = { - title: string; - url: string; - content: string; - rawContent?: string; - score: number; - publishedDate?: string; -}; - -export type SearchResponse = { - answer?: string; - query: string; - responseTime: number; - images: SearchImage[]; - results: SearchResult[]; -}; - -export enum ServiceType { - IMAGE_DESCRIPTION = "image_description", - TRANSCRIPTION = "transcription", - VIDEO = "video", - TEXT_GENERATION = "text_generation", - BROWSER = "browser", - SPEECH_GENERATION = "speech_generation", - PDF = "pdf", - INTIFACE = "intiface", - AWS_S3 = "aws_s3", - BUTTPLUG = "buttplug", - SLACK = "slack", -} - -export enum LoggingLevel { - DEBUG = "debug", - VERBOSE = "verbose", - NONE = "none", -} - -export type KnowledgeItem = { - id: UUID; - content: Content; -}; - -export interface ActionResponse { - like: boolean; - retweet: boolean; - quote?: boolean; - reply?: boolean; -} - -export interface ISlackService extends Service { - client: any; -} - -export enum TokenizerType { - Auto = "auto", - TikToken = "tiktoken", -} - -export enum TranscriptionProvider { - OpenAI = "openai", - Deepgram = "deepgram", - Local = "local", -} diff --git a/packages/plugin-node/README.md b/packages/plugin-node/README.md index 99a5f098508..a9951681292 100644 --- a/packages/plugin-node/README.md +++ b/packages/plugin-node/README.md @@ -6,6 +6,7 @@ Core Node.js plugin for Eliza OS that provides essential services and actions fo The Node plugin serves as a foundational component of Eliza OS, bridging core Node.js capabilities with the Eliza ecosystem. It provides crucial services for file operations, media processing, speech synthesis, and cloud integrations, enabling both local and cloud-based functionality for Eliza agents. + ## Features - **AWS S3 Integration**: File upload and management with AWS S3 @@ -28,13 +29,11 @@ npm install @elizaos/plugin-node The plugin requires various environment variables depending on which services you plan to use: ### Core Settings - ```env OPENAI_API_KEY=your_openai_api_key ``` ### Voice Settings (Optional) - ```env ELEVENLABS_XI_API_KEY=your_elevenlabs_api_key ELEVENLABS_MODEL_ID=eleven_monolingual_v1 @@ -47,7 +46,6 @@ VITS_VOICE=en_US-hfc_female-medium ``` ### AWS Settings (Optional) - ```env AWS_ACCESS_KEY_ID=your_aws_access_key AWS_SECRET_ACCESS_KEY=your_aws_secret_key @@ -71,79 +69,65 @@ elizaOS.registerPlugin(nodePlugin); ## Services ### AwsS3Service - Handles file uploads and management with AWS S3. ### BrowserService - Provides web scraping and content extraction capabilities using Playwright. ### ImageDescriptionService - Processes and analyzes images to generate descriptions. ### LlamaService - Provides local LLM capabilities using LLaMA models. ### PdfService - Extracts and processes text content from PDF files. ### SpeechService - Handles text-to-speech conversion using ElevenLabs and VITS. ### TranscriptionService - Converts speech to text using various providers. ### VideoService - Processes video content, including YouTube video downloads and transcription. ## Actions ### describeImage - Analyzes and generates descriptions for images. ```typescript // Example usage const result = await runtime.executeAction("DESCRIBE_IMAGE", { - imageUrl: "path/to/image.jpg", + imageUrl: "path/to/image.jpg" }); ``` ## Dependencies The plugin requires several peer dependencies: - - `onnxruntime-node`: 1.20.1 - `whatwg-url`: 7.1.0 And trusted dependencies: - - `onnxruntime-node`: 1.20.1 - `sharp`: 0.33.5 ## Safety & Security ### File Operations - - **Path Sanitization**: All file paths are sanitized to prevent directory traversal attacks - **File Size Limits**: Enforced limits on upload sizes - **Type Checking**: Strict file type validation - **Temporary File Cleanup**: Automatic cleanup of temporary files ### API Keys & Credentials - - **Environment Isolation**: Sensitive credentials are isolated in environment variables - **Access Scoping**: Services are initialized with minimum required permissions - **Key Rotation**: Support for credential rotation without service interruption ### Media Processing - - **Resource Limits**: Memory and CPU usage limits for media processing - **Timeout Controls**: Automatic termination of long-running processes - **Format Validation**: Strict media format validation before processing @@ -153,31 +137,25 @@ And trusted dependencies: ### Common Issues 1. **Service Initialization Failures** - ```bash Error: Service initialization failed ``` - - Verify environment variables are properly set - Check service dependencies are installed - Ensure sufficient system permissions 2. **Media Processing Errors** - ```bash Error: Failed to process media file ``` - - Verify file format is supported - Check available system memory - Ensure ffmpeg is properly installed 3. **AWS S3 Connection Issues** - ```bash Error: AWS credentials not configured ``` - - Verify AWS credentials are set - Check S3 bucket permissions - Ensure correct region configuration @@ -185,9 +163,8 @@ Error: AWS credentials not configured ### Debug Mode Enable debug logging for detailed troubleshooting: - ```typescript -process.env.DEBUG = "eliza:plugin-node:*"; +process.env.DEBUG = 'eliza:plugin-node:*'; ``` ### System Requirements @@ -200,105 +177,95 @@ process.env.DEBUG = "eliza:plugin-node:*"; ### Performance Optimization 1. **Cache Management** - - - Regular cleanup of `content_cache` directory - - Implement cache size limits - - Monitor disk usage + - Regular cleanup of `content_cache` directory + - Implement cache size limits + - Monitor disk usage 2. **Memory Usage** - - - Configure max buffer sizes - - Implement streaming for large files - - Monitor memory consumption + - Configure max buffer sizes + - Implement streaming for large files + - Monitor memory consumption 3. **Concurrent Operations** - - Adjust queue size limits - - Configure worker threads - - Monitor process pool + - Adjust queue size limits + - Configure worker threads + - Monitor process pool ## Support For issues and feature requests, please: - 1. Check the troubleshooting guide above 2. Review existing GitHub issues 3. Submit a new issue with: - - System information - - Error logs - - Steps to reproduce + - System information + - Error logs + - Steps to reproduce ## Future Enhancements 1. **File Operations** - - - Enhanced streaming capabilities - - Advanced compression options - - Batch file processing - - File type detection - - Metadata management - - Version control integration + - Enhanced streaming capabilities + - Advanced compression options + - Batch file processing + - File type detection + - Metadata management + - Version control integration 2. **Media Processing** - - - Additional video formats - - Advanced image processing - - Audio enhancement tools - - Real-time processing - - Quality optimization - - Format conversion + - Additional video formats + - Advanced image processing + - Audio enhancement tools + - Real-time processing + - Quality optimization + - Format conversion 3. **Cloud Integration** - - - Multi-cloud support - - Advanced caching - - CDN optimization - - Auto-scaling features - - Cost optimization - - Backup automation + - Multi-cloud support + - Advanced caching + - CDN optimization + - Auto-scaling features + - Cost optimization + - Backup automation 4. **Speech Services** - - - Additional voice models - - Language expansion - - Emotion detection - - Voice cloning - - Real-time synthesis - - Custom voice training + - Additional voice models + - Language expansion + - Emotion detection + - Voice cloning + - Real-time synthesis + - Custom voice training 5. **Browser Automation** - - - Headless optimization - - Parallel processing - - Session management - - Cookie handling - - Proxy support - - Resource optimization + - Headless optimization + - Parallel processing + - Session management + - Cookie handling + - Proxy support + - Resource optimization 6. **Security Features** - - - Enhanced encryption - - Access control - - Audit logging - - Threat detection - - Rate limiting - - Compliance tools + - Enhanced encryption + - Access control + - Audit logging + - Threat detection + - Rate limiting + - Compliance tools 7. **Performance Optimization** - - - Memory management - - CPU utilization - - Concurrent operations - - Resource pooling - - Cache strategies - - Load balancing + - Memory management + - CPU utilization + - Concurrent operations + - Resource pooling + - Cache strategies + - Load balancing 8. **Developer Tools** - - Enhanced debugging - - Testing framework - - Documentation generator - - CLI improvements - - Monitoring tools - - Integration templates + - Enhanced debugging + - Testing framework + - Documentation generator + - CLI improvements + - Monitoring tools + - Integration templates We welcome community feedback and contributions to help prioritize these enhancements. @@ -322,16 +289,14 @@ This plugin integrates with and builds upon several key technologies: - [Sharp](https://sharp.pixelplumbing.com/) - Image processing Special thanks to: - - The Node.js community and all the open-source contributors who make these integrations possible. - The Eliza community for their contributions and feedback. For more information about Node.js capabilities: - - [Node.js Documentation](https://nodejs.org/en/docs/) - [Node.js Developer Portal](https://nodejs.org/en/about/) - [Node.js GitHub Repository](https://github.com/nodejs/node) ## License -This plugin is part of the Eliza project. See the main project repository for license information. +This plugin is part of the Eliza project. See the main project repository for license information. \ No newline at end of file diff --git a/packages/plugin-node/package.json b/packages/plugin-node/package.json index e6381e210c5..24a0520641a 100644 --- a/packages/plugin-node/package.json +++ b/packages/plugin-node/package.json @@ -34,6 +34,7 @@ "@opendocsg/pdf2md": "0.1.32", "@types/uuid": "10.0.0", "alawmulaw": "6.0.0", + "bignumber": "1.1.0", "bignumber.js": "9.1.2", "capsolver-npm": "2.0.2", "cldr-segmentation": "2.2.1", diff --git a/packages/plugin-node/src/services/awsS3.ts b/packages/plugin-node/src/services/awsS3.ts index 0c038c5ef5d..1f94286696a 100644 --- a/packages/plugin-node/src/services/awsS3.ts +++ b/packages/plugin-node/src/services/awsS3.ts @@ -3,7 +3,6 @@ import { IAwsS3Service, Service, ServiceType, - elizaLogger, } from "@elizaos/core"; import { GetObjectCommand, @@ -33,7 +32,7 @@ export class AwsS3Service extends Service implements IAwsS3Service { private runtime: IAgentRuntime | null = null; async initialize(runtime: IAgentRuntime): Promise { - elizaLogger.log("Initializing AwsS3Service"); + console.log("Initializing AwsS3Service"); this.runtime = runtime; this.fileUploadPath = runtime.getSetting("AWS_S3_UPLOAD_PATH") ?? ""; } diff --git a/packages/plugin-node/src/services/browser.ts b/packages/plugin-node/src/services/browser.ts index 4a482f295bd..863b3de4acc 100644 --- a/packages/plugin-node/src/services/browser.ts +++ b/packages/plugin-node/src/services/browser.ts @@ -7,7 +7,6 @@ import { stringToUuid } from "@elizaos/core"; import { PlaywrightBlocker } from "@cliqz/adblocker-playwright"; import CaptchaSolver from "capsolver-npm"; import { Browser, BrowserContext, chromium, Page } from "playwright"; -import { elizaLogger } from "@elizaos/core"; async function generateSummary( runtime: IAgentRuntime, @@ -170,7 +169,7 @@ export class BrowserService extends Service implements IBrowserService { try { if (!this.context) { - elizaLogger.log( + console.log( "Browser context not initialized. Call initializeBrowser() first." ); } @@ -190,7 +189,7 @@ export class BrowserService extends Service implements IBrowserService { const response = await page.goto(url, { waitUntil: "networkidle" }); if (!response) { - elizaLogger.error("Failed to load the page"); + console.log("Failed to load the page"); } if (response.status() === 403 || response.status() === 404) { @@ -217,7 +216,7 @@ export class BrowserService extends Service implements IBrowserService { }); return content; } catch (error) { - elizaLogger.error("Error:", error); + console.error("Error:", error); return { title: url, description: "Error, could not fetch content", @@ -277,7 +276,7 @@ export class BrowserService extends Service implements IBrowserService { }, solution.gRecaptchaResponse); } } catch (error) { - elizaLogger.error("Error solving CAPTCHA:", error); + console.error("Error solving CAPTCHA:", error); } } @@ -313,7 +312,7 @@ export class BrowserService extends Service implements IBrowserService { try { return await this.fetchPageContent(archiveUrl, runtime); } catch (error) { - elizaLogger.error("Error fetching from Internet Archive:", error); + console.error("Error fetching from Internet Archive:", error); } // Try Google Search as a last resort @@ -321,10 +320,8 @@ export class BrowserService extends Service implements IBrowserService { try { return await this.fetchPageContent(googleSearchUrl, runtime); } catch (error) { - elizaLogger.error("Error fetching from Google Search:", error); - elizaLogger.error( - "Failed to fetch content from alternative sources" - ); + console.error("Error fetching from Google Search:", error); + console.error("Failed to fetch content from alternative sources"); return { title: url, description: diff --git a/packages/plugin-node/src/services/image.ts b/packages/plugin-node/src/services/image.ts index 55c29db6d14..bf266163eb5 100644 --- a/packages/plugin-node/src/services/image.ts +++ b/packages/plugin-node/src/services/image.ts @@ -187,12 +187,7 @@ export class ImageDescriptionService ): Promise { for (let attempt = 0; attempt < 3; attempt++) { try { - const shouldUseBase64 = - (isGif || isLocalFile) && - !( - this.runtime.imageModelProvider === - ModelProviderName.OPENAI - ); + const shouldUseBase64 = (isGif || isLocalFile)&& !(this.runtime.imageModelProvider === ModelProviderName.OPENAI); const mimeType = isGif ? "png" : path.extname(imageUrl).slice(1) || "jpeg"; diff --git a/packages/plugin-node/src/services/speech.ts b/packages/plugin-node/src/services/speech.ts index dcf568967ed..25176cc9e81 100644 --- a/packages/plugin-node/src/services/speech.ts +++ b/packages/plugin-node/src/services/speech.ts @@ -115,9 +115,7 @@ async function textToSpeech(runtime: IAgentRuntime, text: string) { status === 401 && errorBody.detail?.status === "quota_exceeded" ) { - elizaLogger.log( - "ElevenLabs quota exceeded, falling back to VITS" - ); + console.log("ElevenLabs quota exceeded, falling back to VITS"); throw new Error("QUOTA_EXCEEDED"); } @@ -179,12 +177,12 @@ async function textToSpeech(runtime: IAgentRuntime, text: string) { let wavStream: Readable; if (audio instanceof Buffer) { - elizaLogger.log("audio is a buffer"); + console.log("audio is a buffer"); wavStream = Readable.from(audio); } else if ("audioChannels" in audio && "sampleRate" in audio) { - elizaLogger.log("audio is a RawAudio"); + console.log("audio is a RawAudio"); const floatBuffer = Buffer.from(audio.audioChannels[0].buffer); - elizaLogger.log("buffer length: ", floatBuffer.length); + console.log("buffer length: ", floatBuffer.length); // Get the sample rate from the RawAudio object const sampleRate = audio.sampleRate; @@ -223,12 +221,12 @@ async function textToSpeech(runtime: IAgentRuntime, text: string) { async function processVitsAudio(audio: any): Promise { let wavStream: Readable; if (audio instanceof Buffer) { - elizaLogger.log("audio is a buffer"); + console.log("audio is a buffer"); wavStream = Readable.from(audio); } else if ("audioChannels" in audio && "sampleRate" in audio) { - elizaLogger.log("audio is a RawAudio"); + console.log("audio is a RawAudio"); const floatBuffer = Buffer.from(audio.audioChannels[0].buffer); - elizaLogger.log("buffer length: ", floatBuffer.length); + console.log("buffer length: ", floatBuffer.length); const sampleRate = audio.sampleRate; const floatArray = new Float32Array(floatBuffer.buffer); diff --git a/packages/plugin-node/src/services/transcription.ts b/packages/plugin-node/src/services/transcription.ts index d8e39ee2dae..daac3bf303f 100644 --- a/packages/plugin-node/src/services/transcription.ts +++ b/packages/plugin-node/src/services/transcription.ts @@ -80,30 +80,26 @@ export class TranscriptionService // 2) If not chosen from character, check .env if (!chosenProvider) { - const envProvider = this.runtime.getSetting( - "TRANSCRIPTION_PROVIDER" - ); + const envProvider = this.runtime.getSetting("TRANSCRIPTION_PROVIDER"); if (envProvider) { switch (envProvider.toLowerCase()) { case "deepgram": - { - const dgKey = - this.runtime.getSetting("DEEPGRAM_API_KEY"); - if (dgKey) { - this.deepgram = createClient(dgKey); - chosenProvider = TranscriptionProvider.Deepgram; - } + { + const dgKey = this.runtime.getSetting("DEEPGRAM_API_KEY"); + if (dgKey) { + this.deepgram = createClient(dgKey); + chosenProvider = TranscriptionProvider.Deepgram; } + } break; case "openai": - { - const openaiKey = - this.runtime.getSetting("OPENAI_API_KEY"); - if (openaiKey) { - this.openai = new OpenAI({ apiKey: openaiKey }); - chosenProvider = TranscriptionProvider.OpenAI; - } + { + const openaiKey = this.runtime.getSetting("OPENAI_API_KEY"); + if (openaiKey) { + this.openai = new OpenAI({ apiKey: openaiKey }); + chosenProvider = TranscriptionProvider.OpenAI; } + } break; case "local": chosenProvider = TranscriptionProvider.Local; @@ -171,34 +167,34 @@ export class TranscriptionService try { fs.accessSync("/usr/local/cuda/bin/nvcc", fs.constants.X_OK); this.isCudaAvailable = true; - elizaLogger.log( + console.log( "CUDA detected. Transcription will use CUDA acceleration." ); // eslint-disable-next-line } catch (_error) { - elizaLogger.log( + console.log( "CUDA not detected. Transcription will run on CPU." ); } } else if (platform === "win32") { const cudaPath = path.join( settings.CUDA_PATH || - "C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v11.0", + "C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v11.0", "bin", "nvcc.exe" ); if (fs.existsSync(cudaPath)) { this.isCudaAvailable = true; - elizaLogger.log( + console.log( "CUDA detected. Transcription will use CUDA acceleration." ); } else { - elizaLogger.log( + console.log( "CUDA not detected. Transcription will run on CPU." ); } } else { - elizaLogger.log( + console.log( "CUDA not supported on this platform. Transcription will run on CPU." ); } @@ -319,9 +315,7 @@ export class TranscriptionService * We'll keep transcribeUsingDefaultLogic() if needed by other code references, * but it’s no longer invoked in the new flow. */ - private async transcribeUsingDefaultLogic( - audioBuffer: ArrayBuffer - ): Promise { + private async transcribeUsingDefaultLogic(audioBuffer: ArrayBuffer): Promise { if (this.deepgram) { return await this.transcribeWithDeepgram(audioBuffer); } else if (this.openai) { diff --git a/packages/plugin-node/src/services/video.ts b/packages/plugin-node/src/services/video.ts index fc3190abea7..447aed67e02 100644 --- a/packages/plugin-node/src/services/video.ts +++ b/packages/plugin-node/src/services/video.ts @@ -6,7 +6,6 @@ import { Service, ServiceType, stringToUuid, - elizaLogger, } from "@elizaos/core"; import ffmpeg from "fluent-ffmpeg"; import fs from "fs"; @@ -64,7 +63,7 @@ export class VideoService extends Service implements IVideoService { }); return outputFile; } catch (error) { - elizaLogger.log("Error downloading media:", error); + console.error("Error downloading media:", error); throw new Error("Failed to download media"); } } @@ -87,7 +86,7 @@ export class VideoService extends Service implements IVideoService { }); return outputFile; } catch (error) { - elizaLogger.log("Error downloading video:", error); + console.error("Error downloading video:", error); throw new Error("Failed to download video"); } } @@ -149,14 +148,14 @@ export class VideoService extends Service implements IVideoService { const cached = await runtime.cacheManager.get(cacheKey); if (cached) { - elizaLogger.log("Returning cached video file"); + console.log("Returning cached video file"); return cached; } - elizaLogger.log("Cache miss, processing video"); - elizaLogger.log("Fetching video info"); + console.log("Cache miss, processing video"); + console.log("Fetching video info"); const videoInfo = await this.fetchVideoInfo(url); - elizaLogger.log("Getting transcript"); + console.log("Getting transcript"); const transcript = await this.getTranscript(url, videoInfo, runtime); const result: Media = { @@ -190,7 +189,7 @@ export class VideoService extends Service implements IVideoService { }; } } catch (error) { - elizaLogger.log("Error downloading MP4 file:", error); + console.error("Error downloading MP4 file:", error); // Fall back to using youtube-dl if direct download fails } } @@ -210,7 +209,7 @@ export class VideoService extends Service implements IVideoService { }); return result; } catch (error) { - elizaLogger.log("Error fetching video info:", error); + console.error("Error fetching video info:", error); throw new Error("Failed to fetch video information"); } } @@ -220,11 +219,11 @@ export class VideoService extends Service implements IVideoService { videoInfo: any, runtime: IAgentRuntime ): Promise { - elizaLogger.log("Getting transcript"); + console.log("Getting transcript"); try { // Check for manual subtitles if (videoInfo.subtitles && videoInfo.subtitles.en) { - elizaLogger.log("Manual subtitles found"); + console.log("Manual subtitles found"); const srtContent = await this.downloadSRT( videoInfo.subtitles.en[0].url ); @@ -236,7 +235,7 @@ export class VideoService extends Service implements IVideoService { videoInfo.automatic_captions && videoInfo.automatic_captions.en ) { - elizaLogger.log("Automatic captions found"); + console.log("Automatic captions found"); const captionUrl = videoInfo.automatic_captions.en[0].url; const captionContent = await this.downloadCaption(captionUrl); return this.parseCaption(captionContent); @@ -247,23 +246,23 @@ export class VideoService extends Service implements IVideoService { videoInfo.categories && videoInfo.categories.includes("Music") ) { - elizaLogger.log("Music video detected, no lyrics available"); + console.log("Music video detected, no lyrics available"); return "No lyrics available."; } // Fall back to audio transcription - elizaLogger.log( - "No subtitles or captions found, falling back to audio transcription" + console.log( + "No captions found, falling back to audio transcription" ); return this.transcribeAudio(url, runtime); } catch (error) { - elizaLogger.log("Error in getTranscript:", error); + console.error("Error in getTranscript:", error); throw error; } } private async downloadCaption(url: string): Promise { - elizaLogger.log("Downloading caption from:", url); + console.log("Downloading caption from:", url); const response = await fetch(url); if (!response.ok) { throw new Error( @@ -274,7 +273,7 @@ export class VideoService extends Service implements IVideoService { } private parseCaption(captionContent: string): string { - elizaLogger.log("Parsing caption"); + console.log("Parsing caption"); try { const jsonContent = JSON.parse(captionContent); if (jsonContent.events) { @@ -284,11 +283,11 @@ export class VideoService extends Service implements IVideoService { .join("") .replace("\n", " "); } else { - elizaLogger.log("Unexpected caption format:", jsonContent); + console.error("Unexpected caption format:", jsonContent); return "Error: Unable to parse captions"; } } catch (error) { - elizaLogger.log("Error parsing caption:", error); + console.error("Error parsing caption:", error); return "Error: Unable to parse captions"; } } @@ -302,7 +301,7 @@ export class VideoService extends Service implements IVideoService { } private async downloadSRT(url: string): Promise { - elizaLogger.log("downloadSRT"); + console.log("downloadSRT"); const response = await fetch(url); return await response.text(); } @@ -311,7 +310,7 @@ export class VideoService extends Service implements IVideoService { url: string, runtime: IAgentRuntime ): Promise { - elizaLogger.log("Preparing audio for transcription..."); + console.log("Preparing audio for transcription..."); const mp4FilePath = path.join( this.dataDir, `${this.getVideoId(url)}.mp4` @@ -324,20 +323,20 @@ export class VideoService extends Service implements IVideoService { if (!fs.existsSync(mp3FilePath)) { if (fs.existsSync(mp4FilePath)) { - elizaLogger.log("MP4 file found. Converting to MP3..."); + console.log("MP4 file found. Converting to MP3..."); await this.convertMp4ToMp3(mp4FilePath, mp3FilePath); } else { - elizaLogger.log("Downloading audio..."); + console.log("Downloading audio..."); await this.downloadAudio(url, mp3FilePath); } } - elizaLogger.log(`Audio prepared at ${mp3FilePath}`); + console.log(`Audio prepared at ${mp3FilePath}`); const audioBuffer = fs.readFileSync(mp3FilePath); - elizaLogger.log(`Audio file size: ${audioBuffer.length} bytes`); + console.log(`Audio file size: ${audioBuffer.length} bytes`); - elizaLogger.log("Starting transcription..."); + console.log("Starting transcription..."); const startTime = Date.now(); const transcriptionService = runtime.getService( ServiceType.TRANSCRIPTION @@ -350,7 +349,7 @@ export class VideoService extends Service implements IVideoService { const transcript = await transcriptionService.transcribe(audioBuffer); const endTime = Date.now(); - elizaLogger.log( + console.log( `Transcription completed in ${(endTime - startTime) / 1000} seconds` ); @@ -368,11 +367,11 @@ export class VideoService extends Service implements IVideoService { .noVideo() .audioCodec("libmp3lame") .on("end", () => { - elizaLogger.log("Conversion to MP3 complete"); + console.log("Conversion to MP3 complete"); resolve(); }) .on("error", (err) => { - elizaLogger.log("Error converting to MP3:", err); + console.error("Error converting to MP3:", err); reject(err); }) .run(); @@ -383,14 +382,14 @@ export class VideoService extends Service implements IVideoService { url: string, outputFile: string ): Promise { - elizaLogger.log("Downloading audio"); + console.log("Downloading audio"); outputFile = outputFile ?? path.join(this.dataDir, `${this.getVideoId(url)}.mp3`); try { if (url.endsWith(".mp4") || url.includes(".mp4?")) { - elizaLogger.log( + console.log( "Direct MP4 file detected, downloading and converting to MP3" ); const tempMp4File = path.join( @@ -417,7 +416,7 @@ export class VideoService extends Service implements IVideoService { .run(); }); } else { - elizaLogger.log( + console.log( "YouTube video detected, downloading audio with youtube-dl" ); await youtubeDl(url, { @@ -430,7 +429,7 @@ export class VideoService extends Service implements IVideoService { } return outputFile; } catch (error) { - elizaLogger.log("Error downloading audio:", error); + console.error("Error downloading audio:", error); throw new Error("Failed to download audio"); } } diff --git a/packages/plugin-node/tsconfig.json b/packages/plugin-node/tsconfig.json index d5059a358bb..2ef05a1844a 100644 --- a/packages/plugin-node/tsconfig.json +++ b/packages/plugin-node/tsconfig.json @@ -3,7 +3,12 @@ "compilerOptions": { "outDir": "dist", "rootDir": "src", - "types": ["node"] + "types": [ + "node" + ] }, - "include": ["src/**/*.ts", "src/**/*.d.ts"] -} + "include": [ + "src/**/*.ts", + "src/**/*.d.ts" + ] +} \ No newline at end of file diff --git a/packages/plugin-twilio/.npmignore b/packages/plugin-twilio/.npmignore new file mode 100644 index 00000000000..078562eceab --- /dev/null +++ b/packages/plugin-twilio/.npmignore @@ -0,0 +1,6 @@ +* + +!dist/** +!package.json +!readme.md +!tsup.config.ts \ No newline at end of file diff --git a/packages/plugin-twilio/ReadMe.txt b/packages/plugin-twilio/ReadMe.txt new file mode 100644 index 00000000000..c32b9723e69 --- /dev/null +++ b/packages/plugin-twilio/ReadMe.txt @@ -0,0 +1,78 @@ +#The ENV file should contain this information + +# Cache Configs +CACHE_STORE=database + +# Discord Configuration +DISCORD_APPLICATION_ID= +DISCORD_API_TOKEN= + +# AI Model API Keys +OPENAI_API_KEY= + +# Twitter/X Configuration +TWITTER_USERNAME= +TWITTER_PASSWORD= +TWITTER_EMAIL= +TWITTER_POLL_INTERVAL=120 # How often (in seconds) the bot should check for interactions +TWITTER_SEARCH_ENABLE=FALSE # Enable timeline search, WARNING this greatly increases your chance of getting banned +#TWITTER_TARGET_USERS= + +# Twilio Part +TWILIO_ACCOUNT_SID= +TWILIO_AUTH_TOKEN= +TWILIO_PHONE_NUMBER= +TWILIO_WHATSAPP_PHONE_NUMBER= + +# Server Configuration +SERVER_PORT=3000 + +# How to use +1. create your .env file , if you don't have it yet and then populate it with the information above, make sure to fill in all information +2. Add this project into your eliza os project under packages +3. using terminal go inside plugin-twilio then type pnpm install twilio +4. go inside your agent folder update the package.json add this "@elizaos/plugin-twilio": "workspace:*" +5. add this inside your Agent index.ts import { twilioPlugin } from "@elizaos/plugin-twilio"; +6. Add twilioPlugin in Agent Runtime still inside Agent index.ts +7. pnpm install +8. pnpm build +9. pmpn start --character="characters/nameofyouragentcharacterfile.character.json" + +#Note: Make sure you have a twilio developer account and it is verified with verified phone number and have enough credits but they provide free small credits when your account is new +visit twilio: https://www.twilio.com +twilio quick start guides: https://www.twilio.com/docs/messaging/quickstart +twilio documentation: https://www.twilio.com/docs/messaging +Free Trial Account: https://www.twilio.com/docs/messaging/guides/how-to-use-your-free-trial-account + +# For WhatsApp guides follow the link below you need to have a separate twilio whatsapp enabled phone number +https://www.twilio.com/docs/whatsapp +https://www.twilio.com/docs/whatsapp/quickstart/node +https://www.twilio.com/docs/whatsapp/getting-started#registering-a-whatsapp-sender +https://www.twilio.com/docs/verify/whatsapp + + +#Some Other Whats App Info that you might need +https://www.twilio.com/docs/whatsapp + +#Twilio Phone Number guidelines +https://www.twilio.com/en-us/guidelines + + +# Clarification this project is intended to be use/place inside elizaos project packages, this can't work alone + +# Sample implementation can be found here +https://github.com/juanc07/AgentSoulSpark + +# Usage Sample + +The message that you want to send must be inside a Single Quote or double quotes + +Example 1: +Please send sms to [phone number], and my message is '[your message here]' +Please send whats app message to [phone number], and my message is '[your message here]' + +Example 2: +Please send sms to [phone number], and my message is "[your message here]" +Please send whats app message to [phone number], and my message is "[your message here]" + +#Note I haven't tested any other complex string or sentence yet, this could be improve ofcourse but for now it works that way \ No newline at end of file diff --git a/packages/plugin-twilio/eslint.config.mjs b/packages/plugin-twilio/eslint.config.mjs new file mode 100644 index 00000000000..92fe5bbebef --- /dev/null +++ b/packages/plugin-twilio/eslint.config.mjs @@ -0,0 +1,3 @@ +import eslintGlobalConfig from "../../eslint.config.mjs"; + +export default [...eslintGlobalConfig]; diff --git a/packages/plugin-twilio/package.json b/packages/plugin-twilio/package.json new file mode 100644 index 00000000000..db5033dea2e --- /dev/null +++ b/packages/plugin-twilio/package.json @@ -0,0 +1,20 @@ +{ + "name": "@elizaos/plugin-twilio", + "version": "0.1.7-alpha.2", + "main": "dist/index.js", + "type": "module", + "types": "dist/index.d.ts", + "dependencies": { + "@elizaos/core": "workspace:*", + "tsup": "8.3.5", + "twilio": "^5.4.0" + }, + "scripts": { + "build": "tsup --format esm --dts", + "dev": "tsup --format esm --dts --watch", + "lint": "eslint --fix --cache ." + }, + "peerDependencies": { + "whatwg-url": "7.1.0" + } +} diff --git a/packages/plugin-twilio/src/actions/index.ts b/packages/plugin-twilio/src/actions/index.ts new file mode 100644 index 00000000000..0326100e5bc --- /dev/null +++ b/packages/plugin-twilio/src/actions/index.ts @@ -0,0 +1,2 @@ +export * from "./sendSms.ts"; +export * from "./sendWhatsAppMessage.ts"; diff --git a/packages/plugin-twilio/src/actions/sendSms.ts b/packages/plugin-twilio/src/actions/sendSms.ts new file mode 100644 index 00000000000..37ecafbdd93 --- /dev/null +++ b/packages/plugin-twilio/src/actions/sendSms.ts @@ -0,0 +1,169 @@ +import { + ActionExample, + generateText, + HandlerCallback, + IAgentRuntime, + Memory, + ModelClass, + State, + type Action, +} from "@elizaos/core"; +import twilio from 'twilio'; + +export const sendSmsAction: Action = { + name: "SendSms", + similes: [ + "SendSms" + ], + validate: async (_runtime: IAgentRuntime, _message: Memory) => { + return true; + }, + description: + "Send SMS to the mobile number provided by the user", + handler: async ( + _runtime: IAgentRuntime, + _message: Memory, + _state: State, + _options:{[key:string]: unknown}, + _callback: HandlerCallback, + ): Promise => { + // Check if environment variables are set + const accountSid = process.env.TWILIO_ACCOUNT_SID; + const authToken = process.env.TWILIO_AUTH_TOKEN; + + if (!accountSid || !authToken) { + console.error('TWILIO_ACCOUNT_SID or TWILIO_AUTH_TOKEN is not set'); + return false; + } + + // Extract the mobile number from the message + const mobileNumberRegex = /(?:\+|00)(\d{1,3})\s?(\d{3,5})\s?(\d{4,10})/; // This regex matches numbers like +1 123 4567890 or 001 123 4567890 + const text = (_message.content as { text?: string })?.text || ''; + const matches = text.match(mobileNumberRegex); + + const messageRegex = /(['"])(.*?)\1/; + const messageMatch = text.match(messageRegex); + + let mobileNumberProvidedByUser = null; + let messageToSendFromUser = null; + + if(messageMatch){ + messageToSendFromUser = messageMatch[2]; + } + if (matches) { + // Combine the parts of the number into a single string, removing spaces and plus signs + mobileNumberProvidedByUser = `+${matches[1]}${matches[2]}${matches[3]}`; + }else{ + const alternativeMobileNumberRegex = /\b(\d{3})[-.]?(\d{3})[-.]?(\d{4})\b/; // For formats like 123-456-7890 or 123.456.7890 + if (!mobileNumberProvidedByUser) { + const alternativeMatches = text.match(alternativeMobileNumberRegex); + if (alternativeMatches) { + mobileNumberProvidedByUser = `${alternativeMatches[1]}${alternativeMatches[2]}${alternativeMatches[3]}`; + } + } + } + + const twilioNumber = process.env.TWILIO_PHONE_NUMBER; // Your Twilio phone number + + if (!twilioNumber) { + console.error('Twilio phone number is missing'); + // TODO: generate text to have a different message from AI Agent + _callback({ + text: `Sorry there was an issue send sms, please try again later`, + }); + return false; + } + + const recentMessages = `Extract the phone number from the user recent messages ${_state.recentMessages}`; + + if (!mobileNumberProvidedByUser) { + console.error('Mobile number is missing will try to get the phone number or mobile number from recent messages'); + + mobileNumberProvidedByUser = await generateText( + { + runtime: _runtime, + context: recentMessages, + modelClass: ModelClass.SMALL, + stop: ["\n"], + customSystemPrompt: "only extract the message that the user intend to send and only get the last one" + } + ); + } + + if (!mobileNumberProvidedByUser) { + console.error('Mobile number is missing'); + // TODO: generate text to have a different message from AI Agent + _callback({ + text: `Sorry, there was an issue send sms, please try again later`, + }); + return false; + } + + const recentUserMessages = `Extract the message intended for SMS or text: ${_state.recentMessages}`; + + if (!messageToSendFromUser) { + console.error('messageToSendFromUser is missing will try to get the user message from recent messages'); + + messageToSendFromUser = await generateText( + { + runtime: _runtime, + context: recentUserMessages, + modelClass: ModelClass.SMALL, + stop: ["\n"] + } + ); + } + + if(messageToSendFromUser==null){ + console.error('messageToSendFromUser is empty or null'); + + // TODO: generate text to have a different message from AI Agent + _callback({ + text: `Sorry there was an issue sending the WhatsApp message, please try again later`, + }); + return false; + } + + try { + // Initialize Twilio client + const client = twilio(accountSid, authToken); + + // Send the SMS + const message= await client.messages.create({ + body: messageToSendFromUser, // The message body + to: mobileNumberProvidedByUser, // The recipient's phone number + from: twilioNumber, // Your Twilio phone number + }); + + // for debug purposes uncomment this + console.log("check twilio message body: ", message); + + const messageFromAgent = `SMS sent successfully to ${mobileNumberProvidedByUser}`; + + // Call the callback to notify the user + _callback({ + text: messageFromAgent, + }); + + return true; + } catch (error) { + console.error('Failed to send SMS:', error); + _callback({ + text: `Failed to send SMS to ${mobileNumberProvidedByUser}`, + }); + return false; + } + }, + examples: [ + [ + { + user: "{{user1}}", + content: { text: "please send my message via sms to target mobile number" }, + }, + { + user: "{{user2}}", + content: { text: "", action: "SEND_SMS" }, + }, + ], + ] as ActionExample[][], +} as Action; \ No newline at end of file diff --git a/packages/plugin-twilio/src/actions/sendWhatsAppMessage.ts b/packages/plugin-twilio/src/actions/sendWhatsAppMessage.ts new file mode 100644 index 00000000000..0c76fd48ca1 --- /dev/null +++ b/packages/plugin-twilio/src/actions/sendWhatsAppMessage.ts @@ -0,0 +1,142 @@ +import { + ActionExample, + HandlerCallback, + IAgentRuntime, + Memory, + State, + type Action, +} from "@elizaos/core"; +import twilio from 'twilio'; + +export const sendWhatsAppMessageAction: Action = { + name: "SendWhatsAppMessage", + similes: [ + "SendWhatsAppMessage" + ], + validate: async (_runtime: IAgentRuntime, _message: Memory) => { + return true; + }, + description: + "Send a WhatsApp message to the mobile number provided by the user", + handler: async ( + _runtime: IAgentRuntime, + _message: Memory, + _state: State, + _options:{[key:string]: unknown}, + _callback: HandlerCallback, + ): Promise => { + // Check if environment variables are set + const accountSid = process.env.TWILIO_ACCOUNT_SID; + const authToken = process.env.TWILIO_AUTH_TOKEN; + + console.log("CHECK _message: ",_message.content.text); + + if (!accountSid || !authToken) { + console.error('TWILIO_ACCOUNT_SID or TWILIO_AUTH_TOKEN is not set'); + return false; + } + + // Extract the mobile number from the message + const mobileNumberRegex = /(?:\+|00)(\d{1,3})\s?(\d{3,5})\s?(\d{4,10})/; // This regex matches numbers like +1 123 4567890 or 001 123 4567890 + const text = (_message.content as { text?: string })?.text || ''; + const matches = text.match(mobileNumberRegex); + + const messageRegex = /(['"])(.*?)\1/; + const messageMatch = text.match(messageRegex); + + let mobileNumberProvidedByUser = null; + let messageToSendFromUser = null; + + if(messageMatch){ + messageToSendFromUser = messageMatch[2]; + } + if (matches) { + // Combine the parts of the number into a single string, removing spaces and plus signs + mobileNumberProvidedByUser = `+${matches[1]}${matches[2]}${matches[3]}`; + } else { + const alternativeMobileNumberRegex = /\b(\d{3})[-.]?(\d{3})[-.]?(\d{4})\b/; // For formats like 123-456-7890 or 123.456.7890 + if (!mobileNumberProvidedByUser) { + const alternativeMatches = text.match(alternativeMobileNumberRegex); + if (alternativeMatches) { + mobileNumberProvidedByUser = `+${alternativeMatches[1]}${alternativeMatches[2]}${alternativeMatches[3]}`; + } + } + } + + // Your Twilio WhatsApp enabled phone number this is a different from twilio regular phone number + const twilioNumber = process.env.TWILIO_WHATSAPP_PHONE_NUMBER; + + if (!mobileNumberProvidedByUser) { + console.error('Mobile number is missing'); + + //TODO: this can be improve by letting the AI Agent generate his/her own reply for the specific issue + _callback({ + text: `Sorry there was an issue sending the WhatsApp message, please try again later`, + }); + return false; + } + + if (!twilioNumber) { + console.error('Twilio WhatsApp number is missing'); + + //TODO: this can be improve by letting the AI Agent generate his/her own reply for the specific issue + _callback({ + text: `Sorry there was an issue sending the WhatsApp message, please try again later`, + }); + return false; + } + + if(messageToSendFromUser==null){ + console.error('messageToSendFromUser is empty or null'); + + //TODO: this can be improve by letting the AI Agent generate his/her own reply for the specific issue + _callback({ + text: `Sorry there was an issue sending the WhatsApp message, please try again later`, + }); + return false; + } + + try { + // Initialize Twilio client + const client = twilio(accountSid, authToken); + + // Send the WhatsApp message + const message = await client.messages.create({ + body: messageToSendFromUser, // The message body + to: `whatsapp:${mobileNumberProvidedByUser}`, // The recipient's WhatsApp number + from: `whatsapp:${twilioNumber}`, // Your Twilio WhatsApp number + }); + + // for debug purposes uncomment this + console.log("check twilio message body: ", message); + + //TODO: this can be improve by letting the AI Agent generate his/her own reply to user + const messageFromAgent = `WhatsApp message sent successfully to ${mobileNumberProvidedByUser}`; + + // Call the callback to notify the user + _callback({ + text: messageFromAgent, + }); + + return true; + } catch (error) { + console.error('Failed to send WhatsApp message:', error); + _callback({ + text: `Failed to send WhatsApp message to ${mobileNumberProvidedByUser}`, + }); + return false; + } + }, + examples: [ + [ + { + user: "{{user1}}", + content: { text: "please send my message via WhatsApp to target mobile number" }, + }, + { + user: "{{user2}}", + content: { text: "", action: "SEND_WHATSAPP_MESSAGE" }, + }, + ], + ] as ActionExample[][], +} as Action; \ No newline at end of file diff --git a/packages/plugin-twilio/src/index.ts b/packages/plugin-twilio/src/index.ts new file mode 100644 index 00000000000..a10a047d517 --- /dev/null +++ b/packages/plugin-twilio/src/index.ts @@ -0,0 +1,12 @@ +import { Plugin } from "@elizaos/core"; +import { sendWhatsAppMessageAction,sendSmsAction } from "./actions"; +export * as actions from "./actions"; + +export const twilioPlugin: Plugin = { + name: "twilio", + description: "twilio basic send sms action implementation", + actions: [ + sendSmsAction, + sendWhatsAppMessageAction, + ] +}; diff --git a/packages/plugin-twilio/tsconfig.json b/packages/plugin-twilio/tsconfig.json new file mode 100644 index 00000000000..834c4dce269 --- /dev/null +++ b/packages/plugin-twilio/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../core/tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "types": [ + "node" + ] + }, + "include": [ + "src/**/*.ts" + ] +} \ No newline at end of file diff --git a/packages/plugin-twilio/tsup.config.ts b/packages/plugin-twilio/tsup.config.ts new file mode 100644 index 00000000000..e42bf4efeae --- /dev/null +++ b/packages/plugin-twilio/tsup.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + outDir: "dist", + sourcemap: true, + clean: true, + format: ["esm"], // Ensure you're targeting CommonJS + external: [ + "dotenv", // Externalize dotenv to prevent bundling + "fs", // Externalize fs to use Node.js built-in module + "path", // Externalize other built-ins if necessary + "@reflink/reflink", + "@node-llama-cpp", + "https", + "http", + "agentkeepalive", + // Add other modules you want to externalize + ], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dde34c9e572..6957a8c474c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -256,6 +256,9 @@ importers: '@elizaos/plugin-ton': specifier: workspace:* version: link:../packages/plugin-ton + '@elizaos/plugin-twilio': + specifier: workspace:* + version: link:../packages/plugin-twilio '@elizaos/plugin-twitter': specifier: workspace:* version: link:../packages/plugin-twitter @@ -911,8 +914,8 @@ importers: specifier: 6.1.0 version: 6.1.0(rollup@2.79.2) '@rollup/plugin-node-resolve': - specifier: 15.3.0 - version: 15.3.0(rollup@2.79.2) + specifier: 16.0.0 + version: 16.0.0(rollup@2.79.2) '@rollup/plugin-replace': specifier: 5.0.7 version: 5.0.7(rollup@2.79.2) @@ -1605,6 +1608,9 @@ importers: alawmulaw: specifier: 6.0.0 version: 6.0.0 + bignumber: + specifier: 1.1.0 + version: 1.1.0 bignumber.js: specifier: 9.1.2 version: 9.1.2 @@ -2050,6 +2056,21 @@ importers: specifier: 3.2.0 version: 3.2.0 + packages/plugin-twilio: + dependencies: + '@elizaos/core': + specifier: workspace:* + version: link:../core + tsup: + specifier: 8.3.5 + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + twilio: + specifier: ^5.4.0 + version: 5.4.0 + whatwg-url: + specifier: 7.1.0 + version: 7.1.0 + packages/plugin-twitter: dependencies: '@elizaos/core': @@ -7328,6 +7349,15 @@ packages: rollup: optional: true + '@rollup/plugin-node-resolve@16.0.0': + resolution: {integrity: sha512-0FPvAeVUT/zdWoO0jnb/V5BlBsUSNfkIOtFHzMO4H9MOklrmQFY6FduVHKucNb/aTFxvnGhj4MNj/T1oNdDfNg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@rollup/plugin-replace@5.0.7': resolution: {integrity: sha512-PqxSfuorkHz/SPpyngLyg5GCEkOcee9M1bkxiVDr41Pd61mqP1PLOoDPbpl44SB2mQGKwV/In74gqQmGITOhEQ==} engines: {node: '>=14.0.0'} @@ -18202,6 +18232,9 @@ packages: resolution: {integrity: sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==} engines: {node: '>= 10.13.0'} + scmp@2.1.0: + resolution: {integrity: sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==} + scrypt-js@3.0.1: resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} @@ -19435,6 +19468,10 @@ packages: tweetnacl@1.0.3: resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} + twilio@5.4.0: + resolution: {integrity: sha512-kEmxzdOLTzXzUEXIkBVwT1Itxlbp+rtGrQogNfPtSE3EjoEsxrxB/9tdMIEbrsioL8CzTk/+fiKNJekAyHxjuQ==} + engines: {node: '>=14.0'} + twitter-api-v2@1.19.0: resolution: {integrity: sha512-jfG4aapNPM9+4VxNxn0TXvD8Qj8NmVx6cY0hp5K626uZ41qXPaJz33Djd3y6gfHF/+W29+iZz0Y5qB869d/akA==} @@ -20642,6 +20679,10 @@ packages: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} + xmlbuilder@13.0.2: + resolution: {integrity: sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==} + engines: {node: '>=6.0'} + xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} @@ -29033,25 +29074,25 @@ snapshots: optionalDependencies: rollup: 4.30.0 - '@rollup/plugin-node-resolve@15.3.0(rollup@2.79.2)': + '@rollup/plugin-node-resolve@15.3.0(rollup@3.29.5)': dependencies: - '@rollup/pluginutils': 5.1.4(rollup@2.79.2) + '@rollup/pluginutils': 5.1.4(rollup@3.29.5) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 resolve: 1.22.10 optionalDependencies: - rollup: 2.79.2 + rollup: 3.29.5 - '@rollup/plugin-node-resolve@15.3.0(rollup@3.29.5)': + '@rollup/plugin-node-resolve@16.0.0(rollup@2.79.2)': dependencies: - '@rollup/pluginutils': 5.1.4(rollup@3.29.5) + '@rollup/pluginutils': 5.1.4(rollup@2.79.2) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 resolve: 1.22.10 optionalDependencies: - rollup: 3.29.5 + rollup: 2.79.2 '@rollup/plugin-replace@5.0.7(rollup@2.79.2)': dependencies: @@ -36319,7 +36360,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.3.4 + debug: 4.4.0(supports-color@8.1.1) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -43959,6 +44000,8 @@ snapshots: ajv-formats: 2.1.1(ajv@8.17.1) ajv-keywords: 5.1.0(ajv@8.17.1) + scmp@2.1.0: {} + scrypt-js@3.0.1: {} scryptsy@2.1.0: {} @@ -45543,6 +45586,19 @@ snapshots: tweetnacl@1.0.3: {} + twilio@5.4.0: + dependencies: + axios: 1.7.9(debug@4.4.0) + dayjs: 1.11.13 + https-proxy-agent: 5.0.1 + jsonwebtoken: 9.0.2 + qs: 6.13.1 + scmp: 2.1.0 + xmlbuilder: 13.0.2 + transitivePeerDependencies: + - debug + - supports-color + twitter-api-v2@1.19.0: {} tx2@1.0.5: @@ -47186,6 +47242,8 @@ snapshots: xml-name-validator@5.0.0: {} + xmlbuilder@13.0.2: {} + xmlchars@2.2.0: {} xsalsa20@1.2.0: {}