diff --git a/agent/package.json b/agent/package.json index 40c7eaf032b..5ed28913e47 100644 --- a/agent/package.json +++ b/agent/package.json @@ -67,9 +67,7 @@ "@elizaos/plugin-icp": "workspace:*", "@elizaos/plugin-initia": "workspace:*", "@elizaos/plugin-image-generation": "workspace:*", - "@elizaos/plugin-intiface": "workspace:*", "@elizaos/plugin-lens-network": "workspace:*", - "@elizaos/plugin-letzai": "workspace:*", "@elizaos/plugin-lit": "workspace:*", "@elizaos/plugin-massa": "workspace:*", "@elizaos/plugin-mind-network": "workspace:*", diff --git a/docs/community/Discord/collaborations/3d-ai-tv/chat_2024-12-07.md b/docs/community/Discord/collaborations/3d-ai-tv/chat_2024-12-07.md index a41aa77f3de..062f542cb93 100644 --- a/docs/community/Discord/collaborations/3d-ai-tv/chat_2024-12-07.md +++ b/docs/community/Discord/collaborations/3d-ai-tv/chat_2024-12-07.md @@ -15,7 +15,7 @@ The conversation focused on integrating @bigdookie's artwork as bumpers in their - Do we need video producers? Why is it complicated for comfy stuff to be fast-paced? (asked by [boom](09:56)) - What are the next steps in establishing a Creative Studio and bidding on projects? How does budget influence project success? (asked by [whobody, boom](10:27)) - How will the open-source approach help us? How can Banodoco handle bids on their end? (asked by [boom (10:00)]) -- Can we prompt an engineer to help the story arch or main punchlines for AI-assisted writing? How does it come together with human and AI collaboration in filmmaking? (asked by [boom] (10:05)) +- Can we prompt an engineer to help the story arc or main punchlines for AI-assisted writing? How does it come together with human and AI collaboration in filmmaking? (asked by [boom] (10:05)) ## Who Helped Who diff --git a/docs/community/Discord/development/coders/chat_2024-10-27.md b/docs/community/Discord/development/coders/chat_2024-10-27.md index 5020d493c06..7589eb8ad96 100644 --- a/docs/community/Discord/development/coders/chat_2024-10-27.md +++ b/docs/community/Discord/development/coders/chat_2024-10-27.md @@ -2,7 +2,7 @@ ## Summary -In the chat, Cyfer785 sought assistance for creating a frontend interface that would display AI-generated content in a retro style with live scrolling text, reminiscent of Claude's capabilities but tailored to their own AI pipe output. DegenSpartan and Poe engaged in the conversation, suggesting that Cyfer785 was essentially attempting to replicate features from existing projects like Infinite Backrooms and Claude without adding original value. The discussion highlighted a divergence of interests as Cyfer785's vision did not align with what DegenSpartan and Poe were willing or able to provide, leading to the conclusion that they were not suitable collaborators for this project. Despite some initial enthusiasm from other participants like astr0x., who humorously claimed a share of non-existent profits, the technical focus remained on Cyfer785's request for a unique AI interface development. +In the chat, Cyfer785 sought assistance for creating a frontend interface that would display AI-generated content in a retro style with live scrolling text, reminiscent of Claude's capabilities but tailored to their own AI pipe output. DegenSpartan and Poe engaged in the conversation, suggesting that Cyfer785 was essentially attempting to replicate features from existing projects like Infinite Backrooms and Claude without adding original value. The discussion highlighted a divergence of interests as Cyfer785's vision did not align with what DegenSpartan and Poe were willing or able to provide, leading to the conclusion that they were not suitable collaborators for this project. Despite some initial enthusiasm from other participants like astr0x, who humorously claimed a share of non-existent profits, the technical focus remained on Cyfer785's request for a unique AI interface development. ## FAQ diff --git a/package.json b/package.json index 00d43be54d4..05a1c67f865 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "@polkadot/types-codec": "10.13.1", "@polkadot/keyring": "12.6.2", "@ai-sdk/provider": "1.0.6", - "@ai-sdk/provider-utils": "2.1.2", + "@ai-sdk/provider-utils": "2.1.6", "cookie": "0.7.0", "bs58": "5.0.0", "@coral-xyz/anchor": "0.28.0" diff --git a/packages/client-twitter/src/utils.ts b/packages/client-twitter/src/utils.ts index f62564bef1b..0c64a59d231 100644 --- a/packages/client-twitter/src/utils.ts +++ b/packages/client-twitter/src/utils.ts @@ -83,6 +83,7 @@ export async function buildConversationThread( text: currentTweet.text, source: "twitter", url: currentTweet.permanentUrl, + imageUrls: currentTweet.photos.map((p) => p.url) || [], inReplyTo: currentTweet.inReplyToStatusId ? stringToUuid( currentTweet.inReplyToStatusId + @@ -278,6 +279,7 @@ export async function sendTweet( text: tweet.text, source: "twitter", url: tweet.permanentUrl, + imageUrls: tweet.photos.map((p) => p.url) || [], inReplyTo: tweet.inReplyToStatusId ? stringToUuid( tweet.inReplyToStatusId + "-" + client.runtime.agentId diff --git a/packages/core/package.json b/packages/core/package.json index d5fdfe8f186..d55ef41c1d3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -67,12 +67,12 @@ "@ai-sdk/google-vertex": "0.0.43", "@ai-sdk/groq": "0.0.3", "@ai-sdk/mistral": "1.0.9", - "@ai-sdk/openai": "1.0.5", + "@ai-sdk/openai": "1.1.9", "@ai-sdk/amazon-bedrock": "1.1.0", "@fal-ai/client": "1.2.0", "@tavily/core": "^0.0.2", "@types/uuid": "10.0.0", - "ai": "3.4.33", + "ai": "4.1.16", "anthropic-vertex-ai": "1.0.2", "dotenv": "16.4.5", "fastembed": "1.14.1", @@ -84,7 +84,7 @@ "js-tiktoken": "1.0.15", "langchain": "0.3.6", "ollama-ai-provider": "0.16.1", - "openai": "4.73.0", + "openai": "4.82.0", "pino": "^9.6.0", "pino-pretty": "^13.0.0", "tinyld": "1.3.4", diff --git a/packages/core/src/parsing.ts b/packages/core/src/parsing.ts index ebe4f614403..ffdc7f096ce 100644 --- a/packages/core/src/parsing.ts +++ b/packages/core/src/parsing.ts @@ -152,7 +152,7 @@ export function parseJSONObjectFromText( } catch (e) { console.error("Error parsing JSON:", e); console.error("Text is not JSON", text); - return extractAttributes(parsingText); + return extractAttributes(text); } } else { const objectPattern = /{[\s\S]*?}/; @@ -165,7 +165,7 @@ export function parseJSONObjectFromText( } catch (e) { console.error("Error parsing JSON:", e); console.error("Text is not JSON", text); - return extractAttributes(parsingText); + return extractAttributes(text); } } } diff --git a/packages/plugin-0g/biome.json b/packages/plugin-0g/biome.json new file mode 100644 index 00000000000..818716a6219 --- /dev/null +++ b/packages/plugin-0g/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "organizeImports": { + "enabled": false + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": "error" + }, + "suspicious": { + "noExplicitAny": "error" + }, + "style": { + "useConst": "error", + "useImportType": "off" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "es5" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-0g/eslint.config.mjs b/packages/plugin-0g/eslint.config.mjs deleted file mode 100644 index 92fe5bbebef..00000000000 --- a/packages/plugin-0g/eslint.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import eslintGlobalConfig from "../../eslint.config.mjs"; - -export default [...eslintGlobalConfig]; diff --git a/packages/plugin-0g/package.json b/packages/plugin-0g/package.json index 8fddc8a82bc..89b4ca1f2d1 100644 --- a/packages/plugin-0g/package.json +++ b/packages/plugin-0g/package.json @@ -25,12 +25,16 @@ "tsup": "8.3.5" }, "devDependencies": { + "@biomejs/biome": "1.5.3", "vitest": "^1.2.1" }, "scripts": { "build": "tsup --format esm --dts", "dev": "tsup --format esm --dts --watch", "test": "vitest run", - "lint": "eslint --fix --cache ." + "lint": "biome check src/", + "lint:fix": "biome check --apply src/", + "format": "biome format src/", + "format:fix": "biome format --write src/" } } diff --git a/packages/plugin-0g/src/actions/upload.ts b/packages/plugin-0g/src/actions/upload.ts index 23615831800..639da61a140 100644 --- a/packages/plugin-0g/src/actions/upload.ts +++ b/packages/plugin-0g/src/actions/upload.ts @@ -11,9 +11,9 @@ import { elizaLogger, } from "@elizaos/core"; import { Indexer, ZgFile, getFlowContract } from "@0glabs/0g-ts-sdk"; -import { ethers } from "ethers"; +import { ethers, Wallet } from "ethers"; import { composeContext } from "@elizaos/core"; -import { promises as fs } from "fs"; +import { promises as fs, type Stats } from "node:fs"; import { FileSecurityValidator } from "../utils/security"; import { logSecurityEvent, monitorUpload, monitorFileValidation, monitorCleanup } from '../utils/monitoring'; import { uploadTemplate } from "../templates/upload"; @@ -24,10 +24,10 @@ export interface UploadContent extends Content { function isUploadContent( _runtime: IAgentRuntime, - content: any + content: unknown ): content is UploadContent { elizaLogger.debug("Validating upload content", { content }); - return typeof content.filePath === "string"; + return typeof content === "object" && content !== null && "filePath" in content && typeof (content as UploadContent).filePath === "string"; } export const zgUpload: Action = { @@ -82,7 +82,7 @@ export const zgUpload: Action = { }; // Validate config values - if (isNaN(config.maxFileSize) || config.maxFileSize <= 0) { + if (Number.isNaN(config.maxFileSize) || config.maxFileSize <= 0) { elizaLogger.error("Invalid ZEROG_MAX_FILE_SIZE setting", { value: runtime.getSetting("ZEROG_MAX_FILE_SIZE"), messageId: message.id @@ -117,7 +117,7 @@ export const zgUpload: Action = { runtime: IAgentRuntime, message: Memory, state: State, - _options: any, + _options: Record, callback: HandlerCallback ) => { elizaLogger.info("ZG_UPLOAD action started", { @@ -131,18 +131,20 @@ export const zgUpload: Action = { try { // Update state if needed - if (!state) { + // Initialize or update state + let currentState = state; + if (!currentState) { elizaLogger.debug("No state provided, composing new state"); - state = (await runtime.composeState(message)) as State; + currentState = (await runtime.composeState(message)) as State; } else { elizaLogger.debug("Updating existing state"); - state = await runtime.updateRecentMessageState(state); + currentState = await runtime.updateRecentMessageState(currentState); } // Compose upload context elizaLogger.debug("Composing upload context"); const uploadContext = composeContext({ - state, + state: currentState, template: uploadTemplate, }); @@ -307,7 +309,7 @@ export const zgUpload: Action = { // Start upload monitoring const startTime = Date.now(); - let fileStats; + let fileStats: Stats; try { fileStats = await fs.stat(sanitizedPath); elizaLogger.debug("File stats retrieved", { @@ -365,7 +367,7 @@ export const zgUpload: Action = { const provider = new ethers.JsonRpcProvider(runtime.getSetting("ZEROG_EVM_RPC")); const signer = new ethers.Wallet(runtime.getSetting("ZEROG_PRIVATE_KEY"), provider); const indexer = new Indexer(runtime.getSetting("ZEROG_INDEXER_RPC")); - const flowContract = getFlowContract(runtime.getSetting("ZEROG_FLOW_ADDRESS"), signer); + const flowContract = getFlowContract(runtime.getSetting("ZEROG_FLOW_ADDRESS"), signer as any); // Upload file to ZeroG elizaLogger.info("Starting file upload to ZeroG", { diff --git a/packages/plugin-0g/src/utils/security.ts b/packages/plugin-0g/src/utils/security.ts index 2f84af5dc25..6b2e3df898f 100644 --- a/packages/plugin-0g/src/utils/security.ts +++ b/packages/plugin-0g/src/utils/security.ts @@ -1,5 +1,5 @@ -import { promises as fs } from 'fs'; -import path from 'path'; +import { promises as fs } from 'node:fs'; +import path from 'node:path'; export interface SecurityConfig { maxFileSize: number; diff --git a/packages/plugin-0x/biome.json b/packages/plugin-0x/biome.json new file mode 100644 index 00000000000..818716a6219 --- /dev/null +++ b/packages/plugin-0x/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "organizeImports": { + "enabled": false + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": "error" + }, + "suspicious": { + "noExplicitAny": "error" + }, + "style": { + "useConst": "error", + "useImportType": "off" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "es5" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-0x/package.json b/packages/plugin-0x/package.json index 517c3f96fee..975b84a391b 100644 --- a/packages/plugin-0x/package.json +++ b/packages/plugin-0x/package.json @@ -21,13 +21,21 @@ "scripts": { "build": "tsup --format esm --dts", "dev": "tsup --format esm --dts --watch", - "test": "vitest run" + "test": "vitest run", + "lint": "biome check src/", + "lint:fix": "biome check --apply src/", + "format": "biome format src/", + "format:fix": "biome format --write src/" }, "dependencies": { + "@elizaos/core": "workspace:*", + "whatwg-url": "7.1.0", "@0x/swap-ts-sdk": "2.1.1" }, "devDependencies": { - "tsup": "^8.0.1" + "tsup": "^8.0.1", + "@biomejs/biome": "1.5.3", + "vitest": "^2.1.5" }, "peerDependencies": { "@elizaos/core": "workspace:*", diff --git a/packages/plugin-0x/src/EVMtokenRegistry.ts b/packages/plugin-0x/src/EVMtokenRegistry.ts index 51a674b941d..39075f71a5a 100644 --- a/packages/plugin-0x/src/EVMtokenRegistry.ts +++ b/packages/plugin-0x/src/EVMtokenRegistry.ts @@ -1,9 +1,9 @@ import { elizaLogger } from "@elizaos/core"; import { Chains, - TokenMetadata, - TrustWalletGithubJson, - TrustWalletTokenMetadata, + type TokenMetadata, + type TrustWalletGithubJson, + type TrustWalletTokenMetadata, } from "./types"; import { NATIVE_TOKENS } from "./constants"; diff --git a/packages/plugin-0x/src/actions/getIndicativePrice.ts b/packages/plugin-0x/src/actions/getIndicativePrice.ts index b73ceee4ca8..7a9a469d46c 100644 --- a/packages/plugin-0x/src/actions/getIndicativePrice.ts +++ b/packages/plugin-0x/src/actions/getIndicativePrice.ts @@ -1,9 +1,9 @@ import { - Action, - IAgentRuntime, - Memory, - State, - HandlerCallback, + type Action, + type IAgentRuntime, + type Memory, + type State, + type HandlerCallback, elizaLogger, composeContext, ModelClass, @@ -13,7 +13,7 @@ import { import { createClientV2 } from "@0x/swap-ts-sdk"; import { getIndicativePriceTemplate } from "../templates"; import { z } from "zod"; -import { Chains, GetIndicativePriceResponse, PriceInquiry } from "../types"; +import { Chains, type GetIndicativePriceResponse, type PriceInquiry } from "../types"; import { parseUnits } from "viem"; import { CHAIN_NAMES, ZX_MEMORY } from "../constants"; import { EVMTokenRegistry } from "../EVMtokenRegistry"; @@ -45,17 +45,17 @@ export const getIndicativePrice: Action = { runtime: IAgentRuntime, message: Memory, state: State, - options: Record, + _options: Record, callback: HandlerCallback ) => { const supportedChains = Object.keys(Chains).join(" | "); - state = !state + const localState = !state ? await runtime.composeState(message, { supportedChains }) : await runtime.updateRecentMessageState(state); const context = composeContext({ - state, + state: localState, template: getIndicativePriceTemplate, }); @@ -86,7 +86,7 @@ export const getIndicativePrice: Action = { text: `Unsupported chain: ${chain}. Supported chains are: ${Object.keys( Chains ) - .filter((k) => isNaN(Number(k))) + .filter((k) => !Number.isNaN(Number(k))) .join(", ")}`, }); return; @@ -148,10 +148,10 @@ export const getIndicativePrice: Action = { // Format amounts to human-readable numbers const buyAmount = Number(price.buyAmount) / - Math.pow(10, buyTokenMetadata.decimals); + (10 ** buyTokenMetadata.decimals); const sellAmount = Number(price.sellAmount) / - Math.pow(10, sellTokenMetadata.decimals); + (10 ** sellTokenMetadata.decimals); await storePriceInquiryToMemory(runtime, message, { sellTokenObject: sellTokenMetadata, @@ -163,13 +163,13 @@ export const getIndicativePrice: Action = { // Updated formatted response to include chain const formattedResponse = [ - `💱 Swap Details:`, - `────────────────`, + "💱 Swap Details:", + "────────────────", `📤 Sell: ${sellAmount.toFixed(4)} ${sellTokenMetadata.symbol}`, `📥 Buy: ${buyAmount.toFixed(4)} ${buyTokenMetadata.symbol}`, `📊 Rate: 1 ${sellTokenMetadata.symbol} = ${(buyAmount / sellAmount).toFixed(4)} ${buyTokenMetadata.symbol}`, `🔗 Chain: ${CHAIN_NAMES[chainId]}`, - `────────────────`, + "────────────────", `💫 Happy with the price? Type 'quote' to continue`, ].join("\n"); diff --git a/packages/plugin-0x/src/actions/getQuote.ts b/packages/plugin-0x/src/actions/getQuote.ts index 9d50beb94d1..d4df553fe71 100644 --- a/packages/plugin-0x/src/actions/getQuote.ts +++ b/packages/plugin-0x/src/actions/getQuote.ts @@ -1,13 +1,13 @@ import { - Action, - IAgentRuntime, - Memory, - State, - HandlerCallback, + type Action, + type IAgentRuntime, + type Memory, + type State, + type HandlerCallback, elizaLogger, MemoryManager, } from "@elizaos/core"; -import { GetQuoteResponse, PriceInquiry, Quote } from "../types"; +import type { GetQuoteResponse, PriceInquiry, Quote } from "../types"; import { formatTokenAmount } from "../utils"; import { CHAIN_NAMES, NATIVE_TOKENS, ZX_MEMORY } from "../constants"; import { createClientV2 } from "@0x/swap-ts-sdk"; @@ -25,8 +25,8 @@ export const getQuote: Action = { handler: async ( runtime: IAgentRuntime, message: Memory, - state: State, - options: Record, + _state: State, + _options: Record, callback: HandlerCallback ) => { const latestPriceInquiry = await retrieveLatestPriceInquiry( @@ -89,7 +89,7 @@ export const getQuote: Action = { const warnings = []; if (quote.issues?.balance) { warnings.push( - `⚠️ Warnings:`, + "⚠️ Warnings:", ` • Insufficient balance (Have ${formatTokenAmount( quote.issues.balance.actual, quote.issues.balance.token, @@ -99,8 +99,8 @@ export const getQuote: Action = { } const formattedResponse = [ - `🎯 Firm Quote Details:`, - `────────────────`, + "🎯 Firm Quote Details:", + "────────────────", // Basic swap details (same as price) `📤 Sell: ${formatTokenAmount( quote.sellAmount, @@ -125,7 +125,7 @@ export const getQuote: Action = { )}`, // Fee breakdown - `💰 Fees Breakdown:`, + "💰 Fees Breakdown:", ` • 0x Protocol Fee: ${formatTokenAmount( quote.fees.zeroExFee?.amount, quote.fees.zeroExFee?.token, @@ -153,8 +153,8 @@ export const getQuote: Action = { ...(warnings.length > 0 ? warnings : []), - `────────────────`, - `💫 Ready to execute? Type 'execute' to continue`, + "────────────────", + "💫 Ready to execute? Type 'execute' to continue", ] .filter(Boolean) .join("\n"); @@ -223,20 +223,20 @@ export const getQuote: Action = { ], }; -const formatTime = (time: string) => { - const expirationDate = new Date(parseInt(time) * 1000); +// const formatTime = (time: string) => { +// const expirationDate = new Date(parseInt(time) * 1000); - // Format: "Mar 15, 2:30 PM" - const formattedTime = expirationDate.toLocaleString(undefined, { - month: "short", - day: "numeric", - hour: "numeric", - minute: "2-digit", - hour12: true, - }); +// // Format: "Mar 15, 2:30 PM" +// const formattedTime = expirationDate.toLocaleString(undefined, { +// month: "short", +// day: "numeric", +// hour: "numeric", +// minute: "2-digit", +// hour12: true, +// }); - return `${formattedTime}`; -}; +// return `${formattedTime}`; +// }; export const retrieveLatestPriceInquiry = async ( runtime: IAgentRuntime, @@ -260,7 +260,7 @@ export const retrieveLatestPriceInquiry = async ( } return null; } catch (error) { - elizaLogger.error(`Failed to retrieve price inquiry: ${error.message}`); + elizaLogger.error("Failed to retrieve price inquiry:", error.message); return null; } }; diff --git a/packages/plugin-0x/src/actions/swap.ts b/packages/plugin-0x/src/actions/swap.ts index 17da2721440..2183d8be3d9 100644 --- a/packages/plugin-0x/src/actions/swap.ts +++ b/packages/plugin-0x/src/actions/swap.ts @@ -1,16 +1,16 @@ import { - Action, - IAgentRuntime, - Memory, - State, - HandlerCallback, + type Action, + type IAgentRuntime, + type Memory, + type State, + type HandlerCallback, elizaLogger, MemoryManager, } from "@elizaos/core"; -import { Hex, numberToHex, concat } from "viem"; +import { type Hex, numberToHex, concat } from "viem"; import { CHAIN_EXPLORERS, ZX_MEMORY } from "../constants"; import { getWalletClient } from "../hooks.ts/useGetWalletClient"; -import { Quote } from "../types"; +import type { Quote } from "../types"; export const swap: Action = { name: "EXECUTE_SWAP_0X", @@ -31,8 +31,8 @@ export const swap: Action = { handler: async ( runtime: IAgentRuntime, message: Memory, - state: State, - options: Record, + _state: State, + _options: Record, callback: HandlerCallback ) => { const latestQuote = await retrieveLatestQuote(runtime, message); @@ -99,13 +99,12 @@ export const swap: Action = { content: { hash: txHash, status: "success" }, }); return true; - } else { - callback({ - text: `❌ Swap failed! Check transaction: ${CHAIN_EXPLORERS[chainId]}/tx/${txHash}`, - content: { hash: txHash, status: "failed" }, - }); - return false; } + callback({ + text: `❌ Swap failed! Check transaction: ${CHAIN_EXPLORERS[chainId]}/tx/${txHash}`, + content: { hash: txHash, status: "failed" }, + }); + return false; } catch (error) { elizaLogger.error("Swap execution failed:", error); callback({ diff --git a/packages/plugin-0x/src/constants.ts b/packages/plugin-0x/src/constants.ts index f51220a229a..f161bbb0729 100644 --- a/packages/plugin-0x/src/constants.ts +++ b/packages/plugin-0x/src/constants.ts @@ -1,4 +1,4 @@ -import { Chains, TokenMetadata } from "./types"; +import { Chains, type TokenMetadata } from "./types"; export const ZX_MEMORY = { price: { diff --git a/packages/plugin-0x/src/hooks.ts/useGetWalletClient.ts b/packages/plugin-0x/src/hooks.ts/useGetWalletClient.ts index 5bc7238bb3b..25516a3808d 100644 --- a/packages/plugin-0x/src/hooks.ts/useGetWalletClient.ts +++ b/packages/plugin-0x/src/hooks.ts/useGetWalletClient.ts @@ -3,8 +3,8 @@ import { http, publicActions, createTestClient, - WalletClient, - PublicClient, + type WalletClient, + type PublicClient, walletActions, } from "viem"; diff --git a/packages/plugin-0x/src/index.ts b/packages/plugin-0x/src/index.ts index f7f493eae20..a8fd1471137 100644 --- a/packages/plugin-0x/src/index.ts +++ b/packages/plugin-0x/src/index.ts @@ -1,4 +1,4 @@ -import { Plugin } from "@elizaos/core"; +import type { Plugin } from "@elizaos/core"; import { getIndicativePrice } from "./actions/getIndicativePrice"; import { getQuote } from "./actions/getQuote"; import { swap } from "./actions/swap"; diff --git a/packages/plugin-3d-generation/biome.json b/packages/plugin-3d-generation/biome.json new file mode 100644 index 00000000000..818716a6219 --- /dev/null +++ b/packages/plugin-3d-generation/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "organizeImports": { + "enabled": false + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": "error" + }, + "suspicious": { + "noExplicitAny": "error" + }, + "style": { + "useConst": "error", + "useImportType": "off" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "es5" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-3d-generation/package.json b/packages/plugin-3d-generation/package.json index cd3e821b0ad..85e57b61212 100644 --- a/packages/plugin-3d-generation/package.json +++ b/packages/plugin-3d-generation/package.json @@ -24,12 +24,17 @@ "whatwg-url": "7.1.0" }, "devDependencies": { + "@biomejs/biome": "1.5.3", "vitest": "^2.1.5" }, "scripts": { "build": "tsup --format esm --dts", "dev": "tsup --format esm --dts --watch", - "test": "vitest run" + "test": "vitest run", + "lint": "biome check src/", + "lint:fix": "biome check --apply src/", + "format": "biome format src/", + "format:fix": "biome format --write src/" }, "peerDependencies": { "whatwg-url": "7.1.0" diff --git a/packages/plugin-3d-generation/src/index.ts b/packages/plugin-3d-generation/src/index.ts index f0d2bb2e5db..b6b516bee19 100644 --- a/packages/plugin-3d-generation/src/index.ts +++ b/packages/plugin-3d-generation/src/index.ts @@ -10,10 +10,10 @@ import type { import { fal } from "@fal-ai/client"; import { FAL_CONSTANTS } from "./constants"; -import * as fs from "fs"; -import { Buffer } from "buffer"; -import * as path from "path"; -import * as process from "process"; +import * as fs from "node:fs"; +import { Buffer } from "node:buffer"; +import * as path from "node:path"; +import * as process from "node:process"; const generate3D = async (prompt: string, runtime: IAgentRuntime) => { process.env["FAL_KEY"] = @@ -84,7 +84,7 @@ const ThreeDGeneration: Action = { runtime: IAgentRuntime, message: Memory, _state: State, - _options: any, + _options: Record, callback: HandlerCallback ) => { elizaLogger.log("3D generation request:", message); diff --git a/packages/plugin-abstract/biome.json b/packages/plugin-abstract/biome.json new file mode 100644 index 00000000000..818716a6219 --- /dev/null +++ b/packages/plugin-abstract/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "organizeImports": { + "enabled": false + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": "error" + }, + "suspicious": { + "noExplicitAny": "error" + }, + "style": { + "useConst": "error", + "useImportType": "off" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "es5" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-abstract/eslint.config.mjs b/packages/plugin-abstract/eslint.config.mjs deleted file mode 100644 index 92fe5bbebef..00000000000 --- a/packages/plugin-abstract/eslint.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import eslintGlobalConfig from "../../eslint.config.mjs"; - -export default [...eslintGlobalConfig]; diff --git a/packages/plugin-abstract/package.json b/packages/plugin-abstract/package.json index 4795f922ca6..65a97d495e5 100644 --- a/packages/plugin-abstract/package.json +++ b/packages/plugin-abstract/package.json @@ -25,7 +25,10 @@ "viem": "2.22.2" }, "scripts": { - "lint": "eslint --fix --cache .", + "lint": "biome lint .", + "lint:fix": "biome check --apply .", + "format": "biome format .", + "format:fix": "biome format --write .", "build": "tsup --format esm --no-dts", "dev": "tsup --format esm --no-dts --watch", "test": "vitest run", @@ -33,6 +36,7 @@ "test:coverage": "vitest run --coverage" }, "devDependencies": { + "@biomejs/biome": "1.9.4", "tsup": "8.3.5", "typescript": "4.9", "vitest": "^1.0.0" diff --git a/packages/plugin-abstract/src/actions/deployTokenAction.ts b/packages/plugin-abstract/src/actions/deployTokenAction.ts index e06a22fbf8b..83c30946a26 100644 --- a/packages/plugin-abstract/src/actions/deployTokenAction.ts +++ b/packages/plugin-abstract/src/actions/deployTokenAction.ts @@ -99,15 +99,17 @@ export const deployTokenAction: Action = { ): Promise => { elizaLogger.log("Starting Abstract DEPLOY_TOKEN handler..."); - if (!state) { - state = (await runtime.composeState(message)) as State; - } else { - state = await runtime.updateRecentMessageState(state); - } + // Initialize or update state + let currentState = state; + if (!currentState) { + currentState = (await runtime.composeState(message)) as State; + } else { + currentState = await runtime.updateRecentMessageState(currentState); + } - state.currentMessage = `${state.recentMessagesData[1].content.text}`; + currentState.currentMessage = `${currentState.recentMessagesData[1].content.text}`; const deployContext = composeContext({ - state, + state: currentState, template: deployTemplate, }); diff --git a/packages/plugin-abstract/src/actions/getBalanceAction.ts b/packages/plugin-abstract/src/actions/getBalanceAction.ts index 9b4c68ecd00..602fe375676 100644 --- a/packages/plugin-abstract/src/actions/getBalanceAction.ts +++ b/packages/plugin-abstract/src/actions/getBalanceAction.ts @@ -76,7 +76,7 @@ export const getBalanceAction: Action = { "BALANCE_CHECK", "TOKEN_BALANCE", ], - validate: async (runtime: IAgentRuntime, message: Memory) => { + validate: async (runtime: IAgentRuntime, _message: Memory) => { await validateAbstractConfig(runtime); return true; }, @@ -90,17 +90,18 @@ export const getBalanceAction: Action = { ): Promise => { elizaLogger.log("Starting Abstract GET_BALANCE handler..."); - // Initialize or update state - if (!state) { - state = (await runtime.composeState(message)) as State; - } else { - state = await runtime.updateRecentMessageState(state); - } + // Initialize or update state + let currentState = state; + if (!currentState) { + currentState = (await runtime.composeState(message)) as State; + } else { + currentState = await runtime.updateRecentMessageState(currentState); + } // Compose balance context - state.currentMessage = `${state.recentMessagesData[1].content.text}`; + currentState.currentMessage = `${currentState.recentMessagesData[1].content.text}`; const balanceContext = composeContext({ - state, + state: currentState, template: balanceTemplate, }); diff --git a/packages/plugin-abstract/src/actions/transferAction.ts b/packages/plugin-abstract/src/actions/transferAction.ts index a11eeef8102..4cc3f996cc3 100644 --- a/packages/plugin-abstract/src/actions/transferAction.ts +++ b/packages/plugin-abstract/src/actions/transferAction.ts @@ -26,6 +26,27 @@ import { getTokenByName, } from "../utils/viemHelpers"; +// Define types for Abstract client +interface AbstractTransactionRequest { + chain: typeof abstractTestnet; + to: string; + value: bigint; + kzg: undefined; +} + +interface AbstractContractRequest { + chain: typeof abstractTestnet; + address: string; + abi: typeof erc20Abi; + functionName: string; + args: [string, bigint]; +} + +interface AbstractClient { + sendTransaction: (request: AbstractTransactionRequest) => Promise; + writeContract: (request: AbstractContractRequest) => Promise; +} + const TransferSchema = z.object({ tokenAddress: z.string().optional().nullable(), recipient: z.string(), @@ -107,17 +128,18 @@ export const transferAction: Action = { ): Promise => { elizaLogger.log("Starting Abstract SEND_TOKEN handler..."); - // Initialize or update state - if (!state) { - state = (await runtime.composeState(message)) as State; - } else { - state = await runtime.updateRecentMessageState(state); - } + // Initialize or update state + let currentState = state; + if (!currentState) { + currentState = (await runtime.composeState(message)) as State; + } else { + currentState = await runtime.updateRecentMessageState(currentState); + } // Compose transfer context - state.currentMessage = `${state.recentMessagesData[1].content.text}`; + currentState.currentMessage = `${currentState.recentMessagesData[1].content.text}`; const transferContext = composeContext({ - state, + state: currentState, template: transferTemplate, }); @@ -204,7 +226,7 @@ export const transferAction: Action = { const abstractClient = (await createAbstractClient({ chain: abstractTestnet, signer: account, - })) as any; // biome-ignore lint/suspicious/noExplicitAny: type being exported as never + })) as AbstractClient; if (isEthTransfer) { hash = await abstractClient.sendTransaction({ diff --git a/packages/plugin-agentkit/biome.json b/packages/plugin-agentkit/biome.json new file mode 100644 index 00000000000..818716a6219 --- /dev/null +++ b/packages/plugin-agentkit/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "organizeImports": { + "enabled": false + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": "error" + }, + "suspicious": { + "noExplicitAny": "error" + }, + "style": { + "useConst": "error", + "useImportType": "off" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "es5" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-agentkit/package.json b/packages/plugin-agentkit/package.json index 19a716a51ed..12b633ddc39 100644 --- a/packages/plugin-agentkit/package.json +++ b/packages/plugin-agentkit/package.json @@ -12,6 +12,7 @@ "tsup": "8.3.5" }, "devDependencies": { + "@biomejs/biome": "1.9.4", "vitest": "^1.0.0" }, "scripts": { @@ -19,6 +20,10 @@ "dev": "tsup --format esm --dts --watch", "test": "vitest run", "test:watch": "vitest watch", - "test:coverage": "vitest run --coverage" + "test:coverage": "vitest run --coverage", + "lint": "biome lint .", + "lint:fix": "biome check --apply .", + "format": "biome format .", + "format:fix": "biome format --write ." } } diff --git a/packages/plugin-agentkit/src/actions.ts b/packages/plugin-agentkit/src/actions.ts index 7bf3827c51c..b08c65f927e 100644 --- a/packages/plugin-agentkit/src/actions.ts +++ b/packages/plugin-agentkit/src/actions.ts @@ -37,7 +37,7 @@ export async function getAgentKitActions({ runtime: IAgentRuntime, message: Memory, state: State | undefined, - options?: Record, + _options?: Record, callback?: HandlerCallback ): Promise => { try { @@ -93,7 +93,7 @@ export async function getAgentKitActions({ async function executeToolAction( tool: Tool, - parameters: any, + parameters: unknown, client: CdpAgentkit ): Promise { const toolkit = new CdpToolkit(client); @@ -107,7 +107,7 @@ async function executeToolAction( return await selectedTool.call(parameters); } -function composeParameterContext(tool: any, state: State): string { +function composeParameterContext(tool: Tool, state: State): string { const contextTemplate = `{{recentMessages}} Given the recent messages, extract the following information for the action "${tool.name}": diff --git a/packages/plugin-agentkit/src/provider.ts b/packages/plugin-agentkit/src/provider.ts index f55719e2c14..038a9c0143d 100644 --- a/packages/plugin-agentkit/src/provider.ts +++ b/packages/plugin-agentkit/src/provider.ts @@ -1,6 +1,6 @@ import type { Provider, IAgentRuntime } from "@elizaos/core"; import { CdpAgentkit } from "@coinbase/cdp-agentkit-core"; -import * as fs from "fs"; +import * as fs from "node:fs"; const WALLET_DATA_FILE = "wallet_data.txt"; @@ -46,10 +46,11 @@ export async function getClient(): Promise { } export const walletProvider: Provider = { - async get(runtime: IAgentRuntime): Promise { + async get(_runtime: IAgentRuntime): Promise { try { const client = await getClient(); - const address = (await (client as any).wallet.addresses)[0].id; + // Access wallet addresses using type assertion based on the known structure + const address = (client as unknown as { wallet: { addresses: Array<{ id: string }> } }).wallet.addresses[0].id; return `AgentKit Wallet Address: ${address}`; } catch (error) { console.error("Error in AgentKit provider:", error); diff --git a/packages/plugin-akash/biome.json b/packages/plugin-akash/biome.json new file mode 100644 index 00000000000..818716a6219 --- /dev/null +++ b/packages/plugin-akash/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "organizeImports": { + "enabled": false + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": "error" + }, + "suspicious": { + "noExplicitAny": "error" + }, + "style": { + "useConst": "error", + "useImportType": "off" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "es5" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-akash/package.json b/packages/plugin-akash/package.json index 8da2ca91a7c..40bac98c74c 100644 --- a/packages/plugin-akash/package.json +++ b/packages/plugin-akash/package.json @@ -9,7 +9,10 @@ "build": "tsup", "dev": "tsup --watch", "clean": "rm -rf dist", - "lint:fix": "eslint . --fix", + "lint": "biome lint .", + "lint:fix": "biome check --apply .", + "format": "biome format .", + "format:fix": "biome format --write .", "test": "vitest", "test:watch": "vitest watch", "test:coverage": "vitest run --coverage", @@ -32,15 +35,13 @@ "ora": "^8.0.1" }, "devDependencies": { + "@biomejs/biome": "1.9.4", "@types/dotenv": "^8.2.0", "@types/jest": "^29.5.11", "@types/js-yaml": "^4.0.9", "@types/node": "^20.10.5", - "@typescript-eslint/eslint-plugin": "^6.15.0", - "@typescript-eslint/parser": "^6.15.0", "@vitest/coverage-v8": "^0.34.6", "@vitest/ui": "^0.34.6", - "eslint": "^9.16.0", "tsup": "^8.0.1", "typescript": "^5.3.3", "vite": "^5.0.10", diff --git a/packages/plugin-akash/src/actions/closeDeployment.ts b/packages/plugin-akash/src/actions/closeDeployment.ts index 47c7e17a64b..1b6b9a9fc6d 100644 --- a/packages/plugin-akash/src/actions/closeDeployment.ts +++ b/packages/plugin-akash/src/actions/closeDeployment.ts @@ -311,7 +311,7 @@ export const closeDeploymentAction: Action = { handler: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined, + _state: State | undefined, _options: { [key: string]: unknown } = {}, callback?: HandlerCallback ): Promise => { diff --git a/packages/plugin-akash/src/actions/createCertificate.ts b/packages/plugin-akash/src/actions/createCertificate.ts index 6e026596629..801d0e9863e 100644 --- a/packages/plugin-akash/src/actions/createCertificate.ts +++ b/packages/plugin-akash/src/actions/createCertificate.ts @@ -8,8 +8,8 @@ import type { CertificatePem } from "@akashnetwork/akashjs/build/certificates/ce import { getAkashTypeRegistry } from "@akashnetwork/akashjs/build/stargate"; import { validateAkashConfig } from "../environment"; import { AkashError, AkashErrorCode, withRetry } from "../error/error"; -import * as fs from 'fs'; -import * as path from 'path'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; import { Registry } from "@cosmjs/proto-signing"; import type { SigningStargateClient as AkashSigningStargateClient } from "@akashnetwork/akashjs/node_modules/@cosmjs/stargate"; import { getCertificatePath } from "../utils/paths"; @@ -271,7 +271,7 @@ export const createCertificateAction: Action = { handler: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined, + _state: State | undefined, options: { callback?: HandlerCallback } = {} ): Promise => { const actionId = Date.now().toString(); diff --git a/packages/plugin-akash/src/actions/createDeployment.ts b/packages/plugin-akash/src/actions/createDeployment.ts index 2032961fe44..d21cecd416c 100644 --- a/packages/plugin-akash/src/actions/createDeployment.ts +++ b/packages/plugin-akash/src/actions/createDeployment.ts @@ -13,8 +13,8 @@ import { DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing"; import { SigningStargateClient } from "@cosmjs/stargate"; import { validateAkashConfig } from "../environment"; import { AkashError, AkashErrorCode, withRetry } from "../error/error"; -import * as fs from 'fs'; -import * as path from 'path'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; import { getCertificatePath, getDefaultSDLPath } from "../utils/paths"; // import { fileURLToPath } from 'url'; import { inspectRuntime, isPluginLoaded } from "../runtime_inspect"; diff --git a/packages/plugin-akash/src/actions/getDeploymentApi.ts b/packages/plugin-akash/src/actions/getDeploymentApi.ts index 503be32530e..fc46ac9ed30 100644 --- a/packages/plugin-akash/src/actions/getDeploymentApi.ts +++ b/packages/plugin-akash/src/actions/getDeploymentApi.ts @@ -3,8 +3,8 @@ import type { IAgentRuntime, Memory, State, HandlerCallback, Content, ActionExam import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; import { validateAkashConfig } from "../environment"; import { AkashError, AkashErrorCode } from "../error/error"; -import * as fs from 'fs'; -import * as path from 'path'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; import { getDeploymentsPath } from "../utils/paths"; export interface DeploymentInfo { @@ -48,7 +48,7 @@ async function fetchWithRetry(url: string, options: RequestInit, retries = 3, de }); if (i < retries - 1) { - await sleep(delay * Math.pow(2, i)); // Exponential backoff + await sleep(delay * (2 ** i)); // Exponential backoff continue; } @@ -63,7 +63,7 @@ async function fetchWithRetry(url: string, options: RequestInit, retries = 3, de elizaLogger.warn(`API request error (attempt ${i + 1}/${retries})`, { error: error instanceof Error ? error.message : String(error) }); - await sleep(delay * Math.pow(2, i)); + await sleep(delay * (2 ** i)); } } throw new AkashError( @@ -336,7 +336,7 @@ export const getDeploymentApiAction: Action = { } as ActionExample ]], - validate: async (runtime: IAgentRuntime, message: Memory): Promise => { + validate: async (_runtime: IAgentRuntime, message: Memory): Promise => { elizaLogger.debug("Validating get deployments request", { message }); try { const params = message.content as Partial; @@ -381,7 +381,7 @@ export const getDeploymentApiAction: Action = { handler: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined, + _state: State | undefined, _options: { [key: string]: unknown } = {}, callback?: HandlerCallback ): Promise => { diff --git a/packages/plugin-akash/src/actions/getDeploymentStatus.ts b/packages/plugin-akash/src/actions/getDeploymentStatus.ts index b927a8961df..2dfd2abae5a 100644 --- a/packages/plugin-akash/src/actions/getDeploymentStatus.ts +++ b/packages/plugin-akash/src/actions/getDeploymentStatus.ts @@ -143,7 +143,7 @@ export const getDeploymentStatusAction: Action = { handler: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined, + _state: State | undefined, _options: { [key: string]: unknown } = {}, callback?: HandlerCallback ): Promise => { diff --git a/packages/plugin-akash/src/actions/getGPUPricing.ts b/packages/plugin-akash/src/actions/getGPUPricing.ts index a3a4073a2ea..395fa5796e2 100644 --- a/packages/plugin-akash/src/actions/getGPUPricing.ts +++ b/packages/plugin-akash/src/actions/getGPUPricing.ts @@ -58,23 +58,23 @@ export const getGPUPricingAction: Action = { } as ActionExample ]], - validate: async (runtime: IAgentRuntime, message: Memory): Promise => { + validate: async (_runtime: IAgentRuntime, message: Memory): Promise => { elizaLogger.debug("Validating GPU pricing request", { message }); try { const params = message.content as Partial; // Validate CPU if provided - if (params.cpu !== undefined && (isNaN(params.cpu) || params.cpu <= 0)) { + if (params.cpu !== undefined && (Number.isNaN(params.cpu) || params.cpu <= 0)) { throw new GPUPricingError("CPU units must be a positive number", "INVALID_CPU"); } // Validate memory if provided - if (params.memory !== undefined && (isNaN(params.memory) || params.memory <= 0)) { + if (params.memory !== undefined && (Number.isNaN(params.memory) || params.memory <= 0)) { throw new GPUPricingError("Memory must be a positive number", "INVALID_MEMORY"); } // Validate storage if provided - if (params.storage !== undefined && (isNaN(params.storage) || params.storage <= 0)) { + if (params.storage !== undefined && (Number.isNaN(params.storage) || params.storage <= 0)) { throw new GPUPricingError("Storage must be a positive number", "INVALID_STORAGE"); } @@ -91,9 +91,9 @@ export const getGPUPricingAction: Action = { }, handler: async ( - runtime: IAgentRuntime, + _runtime: IAgentRuntime, message: Memory, - state: State | undefined, + _state: State | undefined, _options: { [key: string]: unknown; } = {}, callback?: HandlerCallback ): Promise => { diff --git a/packages/plugin-akash/src/actions/getManifest.ts b/packages/plugin-akash/src/actions/getManifest.ts index 099ae0f412d..5760617d262 100644 --- a/packages/plugin-akash/src/actions/getManifest.ts +++ b/packages/plugin-akash/src/actions/getManifest.ts @@ -3,8 +3,8 @@ import type { IAgentRuntime, Memory, State, HandlerCallback, Content, ActionExam import { SDL } from "@akashnetwork/akashjs/build/sdl"; import { validateAkashConfig } from "../environment"; import { AkashError, AkashErrorCode } from "../error/error"; -import * as fs from 'fs'; -import * as path from 'path'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; import yaml from 'js-yaml'; // import { getAkashTypeRegistry } from "@akashnetwork/akashjs/build/stargate"; import { getDefaultSDLPath } from "../utils/paths"; @@ -70,7 +70,7 @@ const loadSDLFromFile = (filePath: string): string => { // If we get here, none of the paths worked throw new AkashError( - `SDL file not found in any of the possible locations`, + 'SDL file not found in any of the possible locations', AkashErrorCode.VALIDATION_SDL_FAILED, { filePath, @@ -185,7 +185,7 @@ export const getManifestAction: Action = { handler: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined, + _state: State | undefined, _options: { [key: string]: unknown; } = {}, callback?: HandlerCallback ): Promise => { diff --git a/packages/plugin-akash/src/actions/getProvidersList.ts b/packages/plugin-akash/src/actions/getProvidersList.ts index 8a01783fd27..e8449b82e55 100644 --- a/packages/plugin-akash/src/actions/getProvidersList.ts +++ b/packages/plugin-akash/src/actions/getProvidersList.ts @@ -162,7 +162,7 @@ export const getProvidersListAction: Action = { } as ActionExample ]], - validate: async (runtime: IAgentRuntime, message: Memory): Promise => { + validate: async (_runtime: IAgentRuntime, message: Memory): Promise => { elizaLogger.debug("Validating get providers list request", { message }); try { const params = message.content as Partial; @@ -210,7 +210,7 @@ export const getProvidersListAction: Action = { handler: async ( runtime: IAgentRuntime, message: Memory, - state: State | undefined, + _state: State | undefined, _options: { [key: string]: unknown; } = {}, callback?: HandlerCallback ): Promise => { diff --git a/packages/plugin-akash/src/error/error.ts b/packages/plugin-akash/src/error/error.ts index 0ddbdc603ee..9b6adeed193 100644 --- a/packages/plugin-akash/src/error/error.ts +++ b/packages/plugin-akash/src/error/error.ts @@ -1,4 +1,3 @@ - export enum AkashErrorCategory { WALLET = 'WALLET', DEPLOYMENT = 'DEPLOYMENT', @@ -117,7 +116,7 @@ export async function withRetry( } catch (error) { lastError = error as Error; if (i < maxRetries - 1) { - await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, i))); + await new Promise(resolve => setTimeout(resolve, delay * (2 ** i))); } } } diff --git a/packages/plugin-akash/src/index.ts b/packages/plugin-akash/src/index.ts index fdd7f59f84d..5ff80a4aca3 100644 --- a/packages/plugin-akash/src/index.ts +++ b/packages/plugin-akash/src/index.ts @@ -1,4 +1,4 @@ -import { Plugin} from "@elizaos/core"; +import type { Plugin} from "@elizaos/core"; import chalk from 'chalk'; import Table from 'cli-table3'; import ora from 'ora'; diff --git a/packages/plugin-akash/src/runtime_inspect.ts b/packages/plugin-akash/src/runtime_inspect.ts index 25b5aee39fd..985b6d0d466 100644 --- a/packages/plugin-akash/src/runtime_inspect.ts +++ b/packages/plugin-akash/src/runtime_inspect.ts @@ -56,7 +56,7 @@ export function isPluginLoaded(runtime: IAgentRuntime, pluginName: string): bool // Check plugins array const plugins = (runtime as any).plugins as Plugin[]; if (!plugins) { - elizaLogger.warn(`No plugins array found in runtime`); + elizaLogger.warn('No plugins array found in runtime'); return false; } @@ -70,7 +70,7 @@ export function isPluginLoaded(runtime: IAgentRuntime, pluginName: string): bool // Check if actions are registered const actions = (runtime as any).actions as Action[]; if (!actions || !actions.length) { - elizaLogger.warn(`No actions found in runtime`); + elizaLogger.warn('No actions found in runtime'); return false; } diff --git a/packages/plugin-allora/biome.json b/packages/plugin-allora/biome.json new file mode 100644 index 00000000000..818716a6219 --- /dev/null +++ b/packages/plugin-allora/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "organizeImports": { + "enabled": false + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": "error" + }, + "suspicious": { + "noExplicitAny": "error" + }, + "style": { + "useConst": "error", + "useImportType": "off" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "es5" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-allora/package.json b/packages/plugin-allora/package.json index 6dee6303b49..754cf4e8813 100644 --- a/packages/plugin-allora/package.json +++ b/packages/plugin-allora/package.json @@ -5,16 +5,24 @@ "type": "module", "types": "dist/index.d.ts", "dependencies": { - "@alloralabs/allora-sdk": "0.0.4", "@elizaos/core": "workspace:*", "node-cache": "5.1.2", "vitest": "2.1.8", "@alloralabs/allora-sdk": "^0.1.0" }, + "devDependencies": { + "@biomejs/biome": "1.9.4", + "tsup": "8.3.5", + "vitest": "2.1.8" + }, "scripts": { "build": "tsup --format esm --dts", "dev": "tsup --format esm --dts --watch", - "test": "vitest run" + "test": "vitest run", + "lint": "biome lint .", + "lint:fix": "biome check --apply .", + "format": "biome format .", + "format:fix": "biome format --write ." }, "peerDependencies": { "whatwg-url": "7.1.0" diff --git a/packages/plugin-allora/src/actions/getInference.ts b/packages/plugin-allora/src/actions/getInference.ts index d875aa35994..9b1c7acb823 100644 --- a/packages/plugin-allora/src/actions/getInference.ts +++ b/packages/plugin-allora/src/actions/getInference.ts @@ -36,22 +36,23 @@ export const getInferenceAction: Action = { runtime: IAgentRuntime, message: Memory, state: State, - options: { [key: string]: unknown }, + _options: { [key: string]: unknown }, callback: HandlerCallback ): Promise => { // Initialize or update state - if (!state) { - state = (await runtime.composeState(message)) as State; + let currentState = state; + if (!currentState) { + currentState = (await runtime.composeState(message)) as State; } else { - state = await runtime.updateRecentMessageState(state); + currentState = await runtime.updateRecentMessageState(currentState); } // Get Allora topics information from the provider - state.alloraTopics = await topicsProvider.get(runtime, message, state); + currentState.alloraTopics = await topicsProvider.get(runtime, message, currentState); // Compose context for extracting the inference fields const inferenceTopicContext = composeContext({ - state, + state: currentState, template: getInferenceTemplate, }); diff --git a/packages/plugin-allora/src/providers/topics.ts b/packages/plugin-allora/src/providers/topics.ts index 6dd72da36ac..2288c3f22e1 100644 --- a/packages/plugin-allora/src/providers/topics.ts +++ b/packages/plugin-allora/src/providers/topics.ts @@ -23,14 +23,14 @@ export class TopicsProvider implements Provider { const alloraTopics = await this.getAlloraTopics(runtime); // Format the topics into a string to be added to the prompt context - let output = `Allora Network Topics: \n`; + let output = 'Allora Network Topics: \n'; for (const topic of alloraTopics) { output += `Topic Name: ${topic.topic_name}\n`; output += `Topic Description: ${topic.description}\n`; output += `Topic ID: ${topic.topic_id}\n`; output += `Topic is Active: ${topic.is_active}\n`; output += `Topic Updated At: ${topic.updated_at}\n`; - output += `\n`; + output += '\n'; } return output; diff --git a/packages/plugin-coingecko/__tests__/actions/getMarkets.test.ts b/packages/plugin-coingecko/__tests__/actions/getMarkets.test.ts new file mode 100644 index 00000000000..d6c937e2b20 --- /dev/null +++ b/packages/plugin-coingecko/__tests__/actions/getMarkets.test.ts @@ -0,0 +1,281 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { elizaLogger, ModelClass, generateObject, composeContext } from '@elizaos/core'; +import getMarketsAction, { formatCategory } from '../../src/actions/getMarkets'; +import axios from 'axios'; +import * as environment from '../../src/environment'; +import * as categoriesProvider from '../../src/providers/categoriesProvider'; + +vi.mock('axios'); +vi.mock('@elizaos/core', () => ({ + elizaLogger: { + log: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + info: vi.fn(), + success: vi.fn(), + }, + generateObject: vi.fn(), + composeContext: vi.fn(), + ModelClass: { LARGE: 'LARGE', SMALL: 'SMALL' } +})); +vi.mock('../../src/environment', () => ({ + validateCoingeckoConfig: vi.fn(), + getApiConfig: vi.fn() +})); +vi.mock('../../src/providers/categoriesProvider'); + +describe('getMarkets action', () => { + const mockRuntime = { + composeState: vi.fn(), + updateRecentMessageState: vi.fn(), + getPluginConfig: vi.fn(), + }; + + const mockMessage = {}; + const mockState = {}; + const mockCallback = vi.fn(); + const mockConfig = { + COINGECKO_API_KEY: 'test-api-key', + COINGECKO_PRO_API_KEY: null + }; + + const mockCategories = [ + { category_id: 'defi', name: 'DeFi' }, + { category_id: 'nft', name: 'NFT' } + ]; + + beforeEach(() => { + vi.clearAllMocks(); + + // Mock environment validation + vi.mocked(environment.validateCoingeckoConfig).mockResolvedValue(mockConfig); + vi.mocked(environment.getApiConfig).mockReturnValue({ + baseUrl: 'https://api.coingecko.com/api/v3', + apiKey: 'test-api-key', + headerKey: 'x-cg-demo-api-key' + }); + + // Mock categories provider + vi.mocked(categoriesProvider.getCategoriesData).mockResolvedValue(mockCategories); + + // Mock runtime functions + mockRuntime.composeState.mockResolvedValue(mockState); + mockRuntime.updateRecentMessageState.mockResolvedValue(mockState); + mockRuntime.getPluginConfig.mockResolvedValue({ + apiKey: 'test-api-key', + baseUrl: 'https://api.coingecko.com/api/v3' + }); + + // Mock the core functions + vi.mocked(elizaLogger.log).mockImplementation(() => {}); + vi.mocked(elizaLogger.error).mockImplementation(() => {}); + vi.mocked(elizaLogger.success).mockImplementation(() => {}); + vi.mocked(composeContext).mockReturnValue({}); + }); + + describe('formatCategory', () => { + it('should return undefined for undefined input', () => { + expect(formatCategory(undefined, mockCategories)).toBeUndefined(); + }); + + it('should find exact match by category_id', () => { + expect(formatCategory('defi', mockCategories)).toBe('defi'); + }); + + it('should find match by name', () => { + expect(formatCategory('DeFi', mockCategories)).toBe('defi'); + }); + + it('should find partial match', () => { + expect(formatCategory('nf', mockCategories)).toBe('nft'); + }); + + it('should return undefined for no match', () => { + expect(formatCategory('invalid-category', mockCategories)).toBeUndefined(); + }); + }); + + it('should validate coingecko config', async () => { + await getMarketsAction.validate(mockRuntime, mockMessage); + expect(environment.validateCoingeckoConfig).toHaveBeenCalledWith(mockRuntime); + }); + + it('should fetch and format market data', async () => { + const mockResponse = { + data: [ + { + id: 'bitcoin', + symbol: 'btc', + name: 'Bitcoin', + image: 'image_url', + current_price: 50000, + market_cap: 1000000000000, + market_cap_rank: 1, + fully_diluted_valuation: 1100000000000, + total_volume: 30000000000, + high_24h: 51000, + low_24h: 49000, + price_change_24h: 1000, + price_change_percentage_24h: 2, + market_cap_change_24h: 20000000000, + market_cap_change_percentage_24h: 2, + circulating_supply: 19000000, + total_supply: 21000000, + max_supply: 21000000, + ath: 69000, + ath_change_percentage: -27.5, + ath_date: '2021-11-10T14:24:11.849Z', + atl: 67.81, + atl_change_percentage: 73623.12, + atl_date: '2013-07-06T00:00:00.000Z', + last_updated: '2024-01-31T23:00:00.000Z' + } + ] + }; + + vi.mocked(axios.get).mockResolvedValueOnce(mockResponse); + + // Mock the content generation + vi.mocked(generateObject).mockResolvedValueOnce({ + object: { + vs_currency: 'usd', + category: 'defi', + order: 'market_cap_desc', + per_page: 20, + page: 1, + sparkline: false + }, + modelClass: ModelClass.SMALL + }); + + await getMarketsAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(axios.get).toHaveBeenCalledWith( + 'https://api.coingecko.com/api/v3/coins/markets', + expect.objectContaining({ + params: { + vs_currency: 'usd', + category: 'defi', + order: 'market_cap_desc', + per_page: 20, + page: 1, + sparkline: false + } + }) + ); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('Bitcoin (BTC)'), + content: expect.objectContaining({ + markets: expect.arrayContaining([ + expect.objectContaining({ + name: 'Bitcoin', + symbol: 'BTC', + marketCapRank: 1, + currentPrice: 50000 + }) + ]) + }) + })); + }); + + it('should handle invalid category', async () => { + vi.mocked(generateObject).mockResolvedValueOnce({ + object: { + vs_currency: 'usd', + category: 'invalid-category', + order: 'market_cap_desc', + per_page: 20, + page: 1, + sparkline: false + }, + modelClass: ModelClass.SMALL + }); + + await getMarketsAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('Invalid category'), + error: expect.objectContaining({ + message: expect.stringContaining('Invalid category') + }) + })); + }); + + it('should handle API errors gracefully', async () => { + vi.mocked(axios.get).mockRejectedValueOnce(new Error('API Error')); + + vi.mocked(generateObject).mockResolvedValueOnce({ + object: { + vs_currency: 'usd', + order: 'market_cap_desc', + per_page: 20, + page: 1, + sparkline: false + }, + modelClass: ModelClass.SMALL + }); + + await getMarketsAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('Error fetching market data'), + error: expect.objectContaining({ + message: expect.stringContaining('API Error') + }) + })); + }); + + it('should handle rate limit errors', async () => { + const rateLimitError = new Error('Rate limit exceeded'); + Object.assign(rateLimitError, { + response: { status: 429 } + }); + vi.mocked(axios.get).mockRejectedValueOnce(rateLimitError); + + vi.mocked(generateObject).mockResolvedValueOnce({ + object: { + vs_currency: 'usd', + order: 'market_cap_desc', + per_page: 20, + page: 1, + sparkline: false + }, + modelClass: ModelClass.SMALL + }); + + await getMarketsAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('Rate limit exceeded'), + error: expect.objectContaining({ + message: expect.stringContaining('Rate limit exceeded'), + statusCode: 429 + }) + })); + }); + + it('should handle empty response data', async () => { + vi.mocked(axios.get).mockResolvedValueOnce({ data: [] }); + + vi.mocked(generateObject).mockResolvedValueOnce({ + object: { + vs_currency: 'usd', + order: 'market_cap_desc', + per_page: 20, + page: 1, + sparkline: false + }, + modelClass: ModelClass.SMALL + }); + + await getMarketsAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('No market data received'), + error: expect.objectContaining({ + message: expect.stringContaining('No market data received') + }) + })); + }); +}); diff --git a/packages/plugin-coingecko/__tests__/actions/getPrice.test.ts b/packages/plugin-coingecko/__tests__/actions/getPrice.test.ts new file mode 100644 index 00000000000..3c371be397f --- /dev/null +++ b/packages/plugin-coingecko/__tests__/actions/getPrice.test.ts @@ -0,0 +1,208 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { elizaLogger, ModelClass, generateObject, composeContext } from '@elizaos/core'; +import getPriceAction from '../../src/actions/getPrice'; +import axios from 'axios'; +import * as environment from '../../src/environment'; +import * as coinsProvider from '../../src/providers/coinsProvider'; + +vi.mock('axios'); +vi.mock('@elizaos/core', () => ({ + elizaLogger: { + log: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + info: vi.fn(), + success: vi.fn(), + }, + generateObject: vi.fn(), + composeContext: vi.fn(), + ModelClass: { LARGE: 'LARGE' } +})); +vi.mock('../../src/environment', () => ({ + validateCoingeckoConfig: vi.fn(), + getApiConfig: vi.fn() +})); +vi.mock('../../src/providers/coinsProvider'); + +describe('getPrice action', () => { + const mockRuntime = { + composeState: vi.fn(), + updateRecentMessageState: vi.fn(), + getPluginConfig: vi.fn(), + }; + + const mockMessage = {}; + const mockState = {}; + const mockCallback = vi.fn(); + const mockConfig = { + COINGECKO_API_KEY: 'test-api-key', + COINGECKO_PRO_API_KEY: null + }; + + beforeEach(() => { + vi.clearAllMocks(); + + // Mock environment validation + vi.mocked(environment.validateCoingeckoConfig).mockResolvedValue(mockConfig); + vi.mocked(environment.getApiConfig).mockReturnValue({ + baseUrl: 'https://api.coingecko.com/api/v3', + apiKey: 'test-api-key', + headerKey: 'x-cg-demo-api-key' + }); + + // Mock runtime functions + mockRuntime.composeState.mockResolvedValue(mockState); + mockRuntime.updateRecentMessageState.mockResolvedValue(mockState); + mockRuntime.getPluginConfig.mockResolvedValue({ + apiKey: 'test-api-key', + baseUrl: 'https://api.coingecko.com/api/v3' + }); + + // Mock the core functions + vi.mocked(elizaLogger.log).mockImplementation(() => {}); + vi.mocked(elizaLogger.error).mockImplementation(() => {}); + vi.mocked(elizaLogger.success).mockImplementation(() => {}); + vi.mocked(composeContext).mockReturnValue({}); + }); + + it('should validate coingecko config', async () => { + await getPriceAction.validate(mockRuntime, mockMessage); + expect(environment.validateCoingeckoConfig).toHaveBeenCalledWith(mockRuntime); + }); + + it('should fetch and format price data for a single coin', async () => { + const mockPriceResponse = { + data: { + bitcoin: { + usd: 50000, + eur: 42000 + } + } + }; + + const mockCoinsData = [{ + id: 'bitcoin', + name: 'Bitcoin', + symbol: 'btc' + }]; + + vi.mocked(axios.get).mockResolvedValueOnce(mockPriceResponse); + vi.mocked(coinsProvider.getCoinsData).mockResolvedValueOnce(mockCoinsData); + + // Mock the content generation + vi.mocked(generateObject).mockResolvedValueOnce({ + object: { + coinIds: 'bitcoin', + currency: ['usd', 'eur'], + include_market_cap: false, + include_24hr_vol: false, + include_24hr_change: false, + include_last_updated_at: false + }, + modelClass: ModelClass.LARGE + }); + + await getPriceAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(axios.get).toHaveBeenCalledWith( + 'https://api.coingecko.com/api/v3/simple/price', + expect.any(Object) + ); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('Bitcoin (BTC)') + })); + }); + + it('should handle API errors gracefully', async () => { + vi.mocked(axios.get).mockRejectedValueOnce(new Error('API Error')); + + // Mock the content generation + vi.mocked(generateObject).mockResolvedValueOnce({ + object: { + coinIds: 'invalid-coin', + currency: ['usd'], + include_market_cap: false, + include_24hr_vol: false, + include_24hr_change: false, + include_last_updated_at: false + }, + modelClass: ModelClass.LARGE + }); + + await getPriceAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + content: expect.objectContaining({ + error: expect.stringContaining('API Error') + }) + })); + }); + + it('should handle empty response data', async () => { + vi.mocked(axios.get).mockResolvedValueOnce({ data: {} }); + + // Mock the content generation + vi.mocked(generateObject).mockResolvedValueOnce({ + object: { + coinIds: 'non-existent-coin', + currency: ['usd'], + include_market_cap: false, + include_24hr_vol: false, + include_24hr_change: false, + include_last_updated_at: false + }, + modelClass: ModelClass.LARGE + }); + + await getPriceAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + content: expect.objectContaining({ + error: expect.stringContaining('No price data available') + }) + })); + }); + + it('should include additional market data when requested', async () => { + const mockPriceResponse = { + data: { + ethereum: { + usd: 3000, + usd_market_cap: 350000000000, + usd_24h_vol: 20000000000, + usd_24h_change: 5.5, + last_updated_at: 1643673600 + } + } + }; + + const mockCoinsData = [{ + id: 'ethereum', + name: 'Ethereum', + symbol: 'eth' + }]; + + vi.mocked(axios.get).mockResolvedValueOnce(mockPriceResponse); + vi.mocked(coinsProvider.getCoinsData).mockResolvedValueOnce(mockCoinsData); + + // Mock the content generation + vi.mocked(generateObject).mockResolvedValueOnce({ + object: { + coinIds: 'ethereum', + currency: ['usd'], + include_market_cap: true, + include_24hr_vol: true, + include_24hr_change: true, + include_last_updated_at: true + }, + modelClass: ModelClass.LARGE + }); + + await getPriceAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('Market Cap') + })); + }); +}); diff --git a/packages/plugin-coingecko/__tests__/actions/getTopGainersLosers.test.ts b/packages/plugin-coingecko/__tests__/actions/getTopGainersLosers.test.ts new file mode 100644 index 00000000000..3854e593353 --- /dev/null +++ b/packages/plugin-coingecko/__tests__/actions/getTopGainersLosers.test.ts @@ -0,0 +1,251 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { elizaLogger, ModelClass, generateObject, composeContext } from '@elizaos/core'; +import getTopGainersLosersAction from '../../src/actions/getTopGainersLosers'; +import axios from 'axios'; +import * as environment from '../../src/environment'; + +vi.mock('axios'); +vi.mock('@elizaos/core', () => ({ + elizaLogger: { + log: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + info: vi.fn(), + success: vi.fn(), + }, + generateObject: vi.fn(), + composeContext: vi.fn(), + ModelClass: { LARGE: 'LARGE' } +})); +vi.mock('../../src/environment', () => ({ + validateCoingeckoConfig: vi.fn(), + getApiConfig: vi.fn() +})); + +describe('getTopGainersLosers action', () => { + const mockRuntime = { + composeState: vi.fn(), + updateRecentMessageState: vi.fn(), + getPluginConfig: vi.fn(), + }; + + const mockMessage = {}; + const mockState = {}; + const mockCallback = vi.fn(); + const mockConfig = { + COINGECKO_API_KEY: 'test-api-key', + COINGECKO_PRO_API_KEY: null + }; + + beforeEach(() => { + vi.clearAllMocks(); + + // Mock environment validation + vi.mocked(environment.validateCoingeckoConfig).mockResolvedValue(mockConfig); + vi.mocked(environment.getApiConfig).mockReturnValue({ + baseUrl: 'https://api.coingecko.com/api/v3', + apiKey: 'test-api-key', + headerKey: 'x-cg-demo-api-key' + }); + + // Mock runtime functions + mockRuntime.composeState.mockResolvedValue(mockState); + mockRuntime.updateRecentMessageState.mockResolvedValue(mockState); + mockRuntime.getPluginConfig.mockResolvedValue({ + apiKey: 'test-api-key', + baseUrl: 'https://api.coingecko.com/api/v3' + }); + + // Mock the core functions + vi.mocked(elizaLogger.log).mockImplementation(() => {}); + vi.mocked(elizaLogger.error).mockImplementation(() => {}); + vi.mocked(elizaLogger.success).mockImplementation(() => {}); + vi.mocked(composeContext).mockReturnValue({}); + }); + + it('should validate coingecko config', async () => { + await getTopGainersLosersAction.validate(mockRuntime, mockMessage); + expect(environment.validateCoingeckoConfig).toHaveBeenCalledWith(mockRuntime); + }); + + it('should fetch and format top gainers and losers data', async () => { + const mockResponse = { + data: { + top_gainers: [ + { + id: 'bitcoin', + symbol: 'btc', + name: 'Bitcoin', + image: 'image_url', + market_cap_rank: 1, + usd: 50000, + usd_24h_vol: 30000000000, + usd_24h_change: 5.5 + } + ], + top_losers: [ + { + id: 'ethereum', + symbol: 'eth', + name: 'Ethereum', + image: 'image_url', + market_cap_rank: 2, + usd: 2500, + usd_24h_vol: 20000000000, + usd_24h_change: -3.2 + } + ] + } + }; + + vi.mocked(axios.get).mockResolvedValueOnce(mockResponse); + + // Mock the content generation + vi.mocked(generateObject).mockResolvedValueOnce({ + object: { + vs_currency: 'usd', + duration: '24h', + top_coins: '1000' + }, + modelClass: ModelClass.LARGE + }); + + await getTopGainersLosersAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(axios.get).toHaveBeenCalledWith( + 'https://api.coingecko.com/api/v3/coins/top_gainers_losers', + expect.objectContaining({ + params: { + vs_currency: 'usd', + duration: '24h', + top_coins: '1000' + } + }) + ); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('Bitcoin (BTC)'), + content: expect.objectContaining({ + data: expect.objectContaining({ + top_gainers: expect.arrayContaining([ + expect.objectContaining({ + name: 'Bitcoin', + symbol: 'btc', + usd_24h_change: 5.5 + }) + ]), + top_losers: expect.arrayContaining([ + expect.objectContaining({ + name: 'Ethereum', + symbol: 'eth', + usd_24h_change: -3.2 + }) + ]) + }) + }) + })); + }); + + it('should handle API errors gracefully', async () => { + vi.mocked(axios.get).mockRejectedValueOnce(new Error('API Error')); + + // Mock the content generation + vi.mocked(generateObject).mockResolvedValueOnce({ + object: { + vs_currency: 'usd', + duration: '24h', + top_coins: '1000' + }, + modelClass: ModelClass.LARGE + }); + + await getTopGainersLosersAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('Error fetching top gainers/losers data'), + content: expect.objectContaining({ + error: expect.stringContaining('API Error') + }) + })); + }); + + it('should handle rate limit errors', async () => { + const rateLimitError = new Error('Rate limit exceeded'); + Object.assign(rateLimitError, { + response: { status: 429 } + }); + vi.mocked(axios.get).mockRejectedValueOnce(rateLimitError); + + // Mock the content generation + vi.mocked(generateObject).mockResolvedValueOnce({ + object: { + vs_currency: 'usd', + duration: '24h', + top_coins: '1000' + }, + modelClass: ModelClass.LARGE + }); + + await getTopGainersLosersAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('Rate limit exceeded'), + content: expect.objectContaining({ + error: expect.stringContaining('Rate limit exceeded'), + statusCode: 429 + }) + })); + }); + + it('should handle pro plan requirement errors', async () => { + const proPlanError = new Error('Pro plan required'); + Object.assign(proPlanError, { + response: { status: 403 } + }); + vi.mocked(axios.get).mockRejectedValueOnce(proPlanError); + + // Mock the content generation + vi.mocked(generateObject).mockResolvedValueOnce({ + object: { + vs_currency: 'usd', + duration: '24h', + top_coins: '1000' + }, + modelClass: ModelClass.LARGE + }); + + await getTopGainersLosersAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('requires a CoinGecko Pro API key'), + content: expect.objectContaining({ + error: expect.stringContaining('Pro plan required'), + statusCode: 403, + requiresProPlan: true + }) + })); + }); + + it('should handle empty response data', async () => { + vi.mocked(axios.get).mockResolvedValueOnce({ data: null }); + + // Mock the content generation + vi.mocked(generateObject).mockResolvedValueOnce({ + object: { + vs_currency: 'usd', + duration: '24h', + top_coins: '1000' + }, + modelClass: ModelClass.LARGE + }); + + await getTopGainersLosersAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('No data received'), + content: expect.objectContaining({ + error: expect.stringContaining('No data received') + }) + })); + }); +}); diff --git a/packages/plugin-coingecko/__tests__/actions/getTrending.test.ts b/packages/plugin-coingecko/__tests__/actions/getTrending.test.ts new file mode 100644 index 00000000000..32b51f36aaa --- /dev/null +++ b/packages/plugin-coingecko/__tests__/actions/getTrending.test.ts @@ -0,0 +1,220 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { elizaLogger, ModelClass, generateObject, composeContext } from '@elizaos/core'; +import getTrendingAction from '../../src/actions/getTrending'; +import axios from 'axios'; +import * as environment from '../../src/environment'; + +vi.mock('axios'); +vi.mock('@elizaos/core', () => ({ + elizaLogger: { + log: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + info: vi.fn(), + success: vi.fn(), + }, + generateObject: vi.fn(), + composeContext: vi.fn(), + ModelClass: { LARGE: 'LARGE' } +})); +vi.mock('../../src/environment', () => ({ + validateCoingeckoConfig: vi.fn(), + getApiConfig: vi.fn() +})); + +describe('getTrending action', () => { + const mockRuntime = { + composeState: vi.fn(), + updateRecentMessageState: vi.fn(), + getPluginConfig: vi.fn(), + }; + + const mockMessage = {}; + const mockState = {}; + const mockCallback = vi.fn(); + const mockConfig = { + COINGECKO_API_KEY: 'test-api-key', + COINGECKO_PRO_API_KEY: null + }; + + beforeEach(() => { + vi.clearAllMocks(); + + // Mock environment validation + vi.mocked(environment.validateCoingeckoConfig).mockResolvedValue(mockConfig); + vi.mocked(environment.getApiConfig).mockReturnValue({ + baseUrl: 'https://api.coingecko.com/api/v3', + apiKey: 'test-api-key', + headerKey: 'x-cg-demo-api-key' + }); + + // Mock runtime functions + mockRuntime.composeState.mockResolvedValue(mockState); + mockRuntime.updateRecentMessageState.mockResolvedValue(mockState); + mockRuntime.getPluginConfig.mockResolvedValue({ + apiKey: 'test-api-key', + baseUrl: 'https://api.coingecko.com/api/v3' + }); + + // Mock the core functions + vi.mocked(elizaLogger.log).mockImplementation(() => {}); + vi.mocked(elizaLogger.error).mockImplementation(() => {}); + vi.mocked(elizaLogger.success).mockImplementation(() => {}); + vi.mocked(composeContext).mockReturnValue({}); + }); + + it('should validate coingecko config', async () => { + await getTrendingAction.validate(mockRuntime, mockMessage); + expect(environment.validateCoingeckoConfig).toHaveBeenCalledWith(mockRuntime); + }); + + it('should fetch and format trending data', async () => { + const mockTrendingResponse = { + data: { + coins: [ + { + item: { + id: 'bitcoin', + name: 'Bitcoin', + symbol: 'btc', + market_cap_rank: 1, + thumb: 'thumb_url', + large: 'large_url' + } + } + ], + nfts: [ + { + id: 'bored-ape', + name: 'Bored Ape Yacht Club', + symbol: 'BAYC', + thumb: 'thumb_url' + } + ], + categories: [ + { + id: 'defi', + name: 'DeFi' + } + ], + exchanges: [], + icos: [] + } + }; + + vi.mocked(axios.get).mockResolvedValueOnce(mockTrendingResponse); + + // Mock the content generation + vi.mocked(generateObject).mockResolvedValueOnce({ + object: { + include_nfts: true, + include_categories: true + }, + modelClass: ModelClass.LARGE + }); + + await getTrendingAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(axios.get).toHaveBeenCalledWith( + 'https://api.coingecko.com/api/v3/search/trending', + expect.any(Object) + ); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('Bitcoin (BTC)'), + content: expect.objectContaining({ + trending: expect.objectContaining({ + coins: expect.arrayContaining([ + expect.objectContaining({ + name: 'Bitcoin', + symbol: 'BTC', + marketCapRank: 1 + }) + ]), + nfts: expect.arrayContaining([ + expect.objectContaining({ + name: 'Bored Ape Yacht Club', + symbol: 'BAYC' + }) + ]), + categories: expect.arrayContaining([ + expect.objectContaining({ + name: 'DeFi' + }) + ]) + }) + }) + })); + }); + + it('should handle API errors gracefully', async () => { + vi.mocked(axios.get).mockRejectedValueOnce(new Error('API Error')); + + // Mock the content generation + vi.mocked(generateObject).mockResolvedValueOnce({ + object: { + include_nfts: true, + include_categories: true + }, + modelClass: ModelClass.LARGE + }); + + await getTrendingAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('Error fetching trending data'), + content: expect.objectContaining({ + error: expect.stringContaining('API Error') + }) + })); + }); + + it('should handle rate limit errors', async () => { + const rateLimitError = new Error('Rate limit exceeded'); + Object.assign(rateLimitError, { + response: { status: 429 } + }); + vi.mocked(axios.get).mockRejectedValueOnce(rateLimitError); + + // Mock the content generation + vi.mocked(generateObject).mockResolvedValueOnce({ + object: { + include_nfts: true, + include_categories: true + }, + modelClass: ModelClass.LARGE + }); + + await getTrendingAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('Rate limit exceeded'), + content: expect.objectContaining({ + error: expect.stringContaining('Rate limit exceeded'), + statusCode: 429 + }) + })); + }); + + it('should handle empty response data', async () => { + vi.mocked(axios.get).mockResolvedValueOnce({ data: null }); + + // Mock the content generation + vi.mocked(generateObject).mockResolvedValueOnce({ + object: { + include_nfts: true, + include_categories: true + }, + modelClass: ModelClass.LARGE + }); + + await getTrendingAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('Error fetching trending data'), + content: expect.objectContaining({ + error: expect.stringContaining('No data received') + }) + })); + }); +}); diff --git a/packages/plugin-coingecko/__tests__/setup.ts b/packages/plugin-coingecko/__tests__/setup.ts new file mode 100644 index 00000000000..ba8257dbe39 --- /dev/null +++ b/packages/plugin-coingecko/__tests__/setup.ts @@ -0,0 +1,20 @@ +import { vi } from 'vitest'; +import { elizaLogger } from '@elizaos/core'; + +// Mock elizaLogger +vi.mock('@elizaos/core', () => ({ + elizaLogger: { + log: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + info: vi.fn(), + generateObject: vi.fn(), + } +})); + +// Mock fetch +global.fetch = vi.fn(); + +beforeEach(() => { + vi.clearAllMocks(); +}); diff --git a/packages/plugin-coingecko/package.json b/packages/plugin-coingecko/package.json index 1283821cb26..b5086ec15af 100644 --- a/packages/plugin-coingecko/package.json +++ b/packages/plugin-coingecko/package.json @@ -10,12 +10,16 @@ "tsup": "^8.3.5" }, "devDependencies": { - "@biomejs/biome": "1.9.4" + "@biomejs/biome": "1.9.4", + "@vitest/coverage-v8": "^1.2.2", + "vitest": "^1.2.2" }, "scripts": { "build": "tsup --format esm --dts", "dev": "tsup --format esm --dts --watch", "test": "vitest run", + "test:watch": "vitest watch", + "test:coverage": "vitest run --coverage", "clean": "rm -rf dist", "lint": "biome lint .", "lint:fix": "biome check --apply .", diff --git a/packages/plugin-coingecko/vitest.config.ts b/packages/plugin-coingecko/vitest.config.ts new file mode 100644 index 00000000000..419efc958f9 --- /dev/null +++ b/packages/plugin-coingecko/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + setupFiles: ['./__tests__/setup.ts'], + include: ['**/__tests__/**/*.test.ts'], + } +}); diff --git a/packages/plugin-coinmarketcap/__tests__/actions/getPrice.service.test.ts b/packages/plugin-coinmarketcap/__tests__/actions/getPrice.service.test.ts new file mode 100644 index 00000000000..e695eea392d --- /dev/null +++ b/packages/plugin-coinmarketcap/__tests__/actions/getPrice.service.test.ts @@ -0,0 +1,151 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import axios from 'axios'; +import { createPriceService } from '../../src/actions/getPrice/service'; + +vi.mock('axios'); + +describe('PriceService', () => { + const API_KEY = 'test-api-key'; + let priceService: ReturnType; + + beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(axios.create).mockReturnValue(axios); + priceService = createPriceService(API_KEY); + }); + + it('should create axios instance with correct config', () => { + expect(axios.create).toHaveBeenCalledWith({ + baseURL: 'https://pro-api.coinmarketcap.com/v1', + headers: { + 'X-CMC_PRO_API_KEY': API_KEY, + 'Accept': 'application/json' + } + }); + }); + + it('should normalize symbol and currency', async () => { + const mockResponse = { + data: { + data: { + BTC: { + quote: { + USD: { + price: 50000, + market_cap: 1000000000000, + volume_24h: 30000000000, + percent_change_24h: 2.5 + } + } + } + } + } + }; + + vi.mocked(axios.get).mockResolvedValueOnce(mockResponse); + + await priceService.getPrice(' btc ', ' usd '); + + expect(axios.get).toHaveBeenCalledWith( + '/cryptocurrency/quotes/latest', + expect.objectContaining({ + params: { + symbol: 'BTC', + convert: 'USD' + } + }) + ); + }); + + it('should return formatted price data', async () => { + const mockResponse = { + data: { + data: { + BTC: { + quote: { + USD: { + price: 50000, + market_cap: 1000000000000, + volume_24h: 30000000000, + percent_change_24h: 2.5 + } + } + } + } + } + }; + + vi.mocked(axios.get).mockResolvedValueOnce(mockResponse); + + const result = await priceService.getPrice('BTC', 'USD'); + + expect(result).toEqual({ + price: 50000, + marketCap: 1000000000000, + volume24h: 30000000000, + percentChange24h: 2.5 + }); + }); + + it('should handle missing symbol data', async () => { + const mockResponse = { + data: { + data: {} + } + }; + + vi.mocked(axios.get).mockResolvedValueOnce(mockResponse); + + await expect(priceService.getPrice('INVALID', 'USD')) + .rejects + .toThrow('No data found for symbol: INVALID'); + }); + + it('should handle missing quote data', async () => { + const mockResponse = { + data: { + data: { + BTC: { + quote: {} + } + } + } + }; + + vi.mocked(axios.get).mockResolvedValueOnce(mockResponse); + + await expect(priceService.getPrice('BTC', 'INVALID')) + .rejects + .toThrow('No quote data found for currency: INVALID'); + }); + + it('should handle API errors', async () => { + const errorMessage = 'API rate limit exceeded'; + const apiError = new Error(errorMessage); + Object.assign(apiError, { + isAxiosError: true, + response: { + data: { + status: { + error_message: errorMessage + } + } + } + }); + + vi.mocked(axios.get).mockRejectedValueOnce(apiError); + + await expect(priceService.getPrice('BTC', 'USD')) + .rejects + .toThrow(`${errorMessage}`); + }); + + it('should handle non-axios errors', async () => { + const error = new Error('Network error'); + vi.mocked(axios.get).mockRejectedValueOnce(error); + + await expect(priceService.getPrice('BTC', 'USD')) + .rejects + .toThrow('Network error'); + }); +}); diff --git a/packages/plugin-coinmarketcap/__tests__/actions/getPrice.test.ts b/packages/plugin-coinmarketcap/__tests__/actions/getPrice.test.ts new file mode 100644 index 00000000000..f737c282c41 --- /dev/null +++ b/packages/plugin-coinmarketcap/__tests__/actions/getPrice.test.ts @@ -0,0 +1,234 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { elizaLogger, ModelClass, generateObjectDeprecated, composeContext } from '@elizaos/core'; +import getPriceAction from '../../src/actions/getPrice'; +import axios from 'axios'; +import * as environment from '../../src/environment'; + +vi.mock('axios'); +vi.mock('@elizaos/core', () => ({ + elizaLogger: { + log: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + info: vi.fn(), + success: vi.fn(), + }, + generateObjectDeprecated: vi.fn(), + composeContext: vi.fn(), + ModelClass: { SMALL: 'SMALL' } +})); +vi.mock('../../src/environment', () => ({ + validateCoinMarketCapConfig: vi.fn() +})); + +describe('getPrice action', () => { + const mockRuntime = { + composeState: vi.fn(), + updateRecentMessageState: vi.fn(), + getPluginConfig: vi.fn(), + }; + + const mockMessage = {}; + const mockState = {}; + const mockCallback = vi.fn(); + const mockConfig = { + COINMARKETCAP_API_KEY: 'test-api-key' + }; + + beforeEach(() => { + vi.clearAllMocks(); + + // Mock environment validation + vi.mocked(environment.validateCoinMarketCapConfig).mockResolvedValue(mockConfig); + + // Mock runtime functions + mockRuntime.composeState.mockResolvedValue(mockState); + mockRuntime.updateRecentMessageState.mockResolvedValue(mockState); + mockRuntime.getPluginConfig.mockResolvedValue({ + apiKey: 'test-api-key' + }); + + // Mock axios create + vi.mocked(axios.create).mockReturnValue(axios); + + // Mock the core functions + vi.mocked(elizaLogger.log).mockImplementation(() => {}); + vi.mocked(elizaLogger.error).mockImplementation(() => {}); + vi.mocked(elizaLogger.success).mockImplementation(() => {}); + vi.mocked(composeContext).mockReturnValue({}); + }); + + it('should validate coinmarketcap config', async () => { + await getPriceAction.validate(mockRuntime, mockMessage); + expect(environment.validateCoinMarketCapConfig).toHaveBeenCalledWith(mockRuntime); + }); + + it('should fetch and format price data', async () => { + const mockResponse = { + data: { + data: { + BTC: { + quote: { + USD: { + price: 50000, + market_cap: 1000000000000, + volume_24h: 30000000000, + percent_change_24h: 2.5 + } + } + } + } + } + }; + + vi.mocked(axios.get).mockResolvedValueOnce(mockResponse); + + // Mock the content generation + vi.mocked(generateObjectDeprecated).mockResolvedValueOnce({ + symbol: 'BTC', + currency: 'USD' + }); + + await getPriceAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(axios.get).toHaveBeenCalledWith( + '/cryptocurrency/quotes/latest', + expect.objectContaining({ + params: { + symbol: 'BTC', + convert: 'USD' + } + }) + ); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('50000 USD'), + content: expect.objectContaining({ + symbol: 'BTC', + currency: 'USD', + price: 50000, + marketCap: 1000000000000, + volume24h: 30000000000, + percentChange24h: 2.5 + }) + })); + }); + + it('should handle invalid symbol', async () => { + const mockResponse = { + data: { + data: {} + } + }; + + vi.mocked(axios.get).mockResolvedValueOnce(mockResponse); + + // Mock the content generation + vi.mocked(generateObjectDeprecated).mockResolvedValueOnce({ + symbol: 'INVALID', + currency: 'USD' + }); + + await getPriceAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('No data found for symbol'), + content: expect.objectContaining({ + error: expect.stringContaining('No data found for symbol') + }) + })); + }); + + it('should handle invalid currency', async () => { + const mockResponse = { + data: { + data: { + BTC: { + quote: {} + } + } + } + }; + + vi.mocked(axios.get).mockResolvedValueOnce(mockResponse); + + // Mock the content generation + vi.mocked(generateObjectDeprecated).mockResolvedValueOnce({ + symbol: 'BTC', + currency: 'INVALID' + }); + + await getPriceAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('No quote data found for currency'), + content: expect.objectContaining({ + error: expect.stringContaining('No quote data found for currency') + }) + })); + }); + + it('should handle API errors gracefully', async () => { + vi.mocked(axios.get).mockRejectedValueOnce(new Error('API Error')); + + // Mock the content generation + vi.mocked(generateObjectDeprecated).mockResolvedValueOnce({ + symbol: 'BTC', + currency: 'USD' + }); + + await getPriceAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('API Error'), + content: expect.objectContaining({ + error: expect.stringContaining('API Error') + }) + })); + }); + + it('should handle rate limit errors', async () => { + const errorMessage = 'Rate limit exceeded'; + const rateLimitError = new Error(`API Error: ${errorMessage}`); + Object.assign(rateLimitError, { + isAxiosError: true, + response: { + data: { + status: { + error_message: errorMessage + } + } + } + }); + vi.mocked(axios.get).mockRejectedValueOnce(rateLimitError); + + // Mock the content generation + vi.mocked(generateObjectDeprecated).mockResolvedValueOnce({ + symbol: 'BTC', + currency: 'USD' + }); + + await getPriceAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(mockCallback).toHaveBeenCalledWith({ + text: `Error fetching price: API Error: ${errorMessage}`, + content: { error: `API Error: ${errorMessage}` } + }); + }); + + it('should handle invalid content generation', async () => { + // Mock invalid content generation + vi.mocked(generateObjectDeprecated).mockResolvedValueOnce({ + invalidField: 'invalid' + }); + + await getPriceAction.handler(mockRuntime, mockMessage, mockState, {}, mockCallback); + + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ + text: expect.stringContaining('Invalid price check content'), + content: expect.objectContaining({ + error: expect.stringContaining('Invalid price check content') + }) + })); + }); +}); diff --git a/packages/plugin-coinmarketcap/__tests__/setup.ts b/packages/plugin-coinmarketcap/__tests__/setup.ts new file mode 100644 index 00000000000..bbc49909c16 --- /dev/null +++ b/packages/plugin-coinmarketcap/__tests__/setup.ts @@ -0,0 +1,10 @@ +import { vi } from 'vitest'; + +// Mock console methods +global.console = { + ...console, + log: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + info: vi.fn() +}; diff --git a/packages/plugin-coinmarketcap/package.json b/packages/plugin-coinmarketcap/package.json index 47f46660068..cf504b6840f 100644 --- a/packages/plugin-coinmarketcap/package.json +++ b/packages/plugin-coinmarketcap/package.json @@ -20,6 +20,9 @@ "lint": "biome lint .", "lint:fix": "biome check --apply .", "format": "biome format .", - "format:fix": "biome format --write ." + "format:fix": "biome format --write .", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage" } } diff --git a/packages/plugin-coinmarketcap/vitest.config.ts b/packages/plugin-coinmarketcap/vitest.config.ts new file mode 100644 index 00000000000..5c5066f7c54 --- /dev/null +++ b/packages/plugin-coinmarketcap/vitest.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + setupFiles: ['__tests__/setup.ts'], + include: ['__tests__/**/*.test.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + include: ['src/**/*.ts'], + exclude: ['**/*.d.ts', '**/*.test.ts', '**/examples.ts', '**/template.ts'] + } + } +}); diff --git a/packages/plugin-cosmos/src/templates/index.ts b/packages/plugin-cosmos/src/templates/index.ts index e9c82de73d6..074a8e51897 100644 --- a/packages/plugin-cosmos/src/templates/index.ts +++ b/packages/plugin-cosmos/src/templates/index.ts @@ -26,7 +26,7 @@ Respond with a JSON markdown block containing only the extracted values. All fie "chainName": string // The chain name. \`\`\` -Example reponse for the input: "Make transfer 0.0001 OM to mantra1pcnw46km8m5amvf7jlk2ks5std75k73aralhcf on mantrachaintestnet2", the response should be: +Example response for the input: "Make transfer 0.0001 OM to mantra1pcnw46km8m5amvf7jlk2ks5std75k73aralhcf on mantrachaintestnet2", the response should be: \`\`\`json { "symbol": "OM", diff --git a/packages/plugin-email/README.md b/packages/plugin-email/README.md index 3748c7ba65e..8372ebc9964 100644 --- a/packages/plugin-email/README.md +++ b/packages/plugin-email/README.md @@ -18,7 +18,7 @@ The following settings will be declared on your environment variable or inside y ## IMAP Section - `EMAIL_INCOMING_SERVICE`: "imap" -- `EMAIL_INCOMING_HOST`: IMAP Hostname or IP to conenct to +- `EMAIL_INCOMING_HOST`: IMAP Hostname or IP to connect to - `EMAIL_INCOMING_PORT`: the port to connect to (defaults to 993) - `EMAIL_INCOMING_USER`: Username - `EMAIL_INCOMING_PASS`: Password diff --git a/packages/plugin-goplus/src/services/GoplusSecurityService.ts b/packages/plugin-goplus/src/services/GoplusSecurityService.ts index 581f9f2f2e5..0621b0dce0a 100644 --- a/packages/plugin-goplus/src/services/GoplusSecurityService.ts +++ b/packages/plugin-goplus/src/services/GoplusSecurityService.ts @@ -80,7 +80,7 @@ export class GoplusSecurityService extends Service implements IGoplusSecuritySer checkResult = await goPlusManage.dappSecurityAndPhishingSite(obj.url); break; default: - throw new Error("type is invaild") + throw new Error("type is invalid") } elizaLogger.log("checkResult text", checkResult); diff --git a/packages/plugin-story/src/types/api.ts b/packages/plugin-story/src/types/api.ts index 2f22adaa030..6d1d9fc95a1 100644 --- a/packages/plugin-story/src/types/api.ts +++ b/packages/plugin-story/src/types/api.ts @@ -29,7 +29,7 @@ export enum RESOURCE_TYPE { LATEST_TRANSACTIONS = "transactions/latest", } -export enum RESPOURCE_REPONSE_TYPE { +export enum RESOURCE_RESPONSE_TYPE { LICENSE_TOKEN = "LICENSETOKEN", // new version LICENSE_TEMPLATES = "LICENSETEMPLATE", // new version LICENSE_TERMS = "LICENSETERM", // new version @@ -556,4 +556,4 @@ export type LicenseTerms = { licenseTemplate: Address; blockNumber: string; blockTime: string; -}; \ No newline at end of file +}; diff --git a/packages/plugin-twitter/biome.json b/packages/plugin-twitter/biome.json new file mode 100644 index 00000000000..818716a6219 --- /dev/null +++ b/packages/plugin-twitter/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "organizeImports": { + "enabled": false + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": "error" + }, + "suspicious": { + "noExplicitAny": "error" + }, + "style": { + "useConst": "error", + "useImportType": "off" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "es5" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-twitter/eslint.config.mjs b/packages/plugin-twitter/eslint.config.mjs deleted file mode 100644 index 92fe5bbebef..00000000000 --- a/packages/plugin-twitter/eslint.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import eslintGlobalConfig from "../../eslint.config.mjs"; - -export default [...eslintGlobalConfig]; diff --git a/packages/plugin-twitter/package.json b/packages/plugin-twitter/package.json index 09cbeb222a7..a6b181a3d95 100644 --- a/packages/plugin-twitter/package.json +++ b/packages/plugin-twitter/package.json @@ -24,6 +24,7 @@ "tsup": "8.3.5" }, "devDependencies": { + "@biomejs/biome": "1.5.3", "vitest": "^1.0.0" }, "scripts": { @@ -31,6 +32,9 @@ "dev": "tsup --format esm --dts --watch", "test": "vitest run", "test:watch": "vitest", - "lint": "eslint --fix --cache ." + "lint": "biome check src/", + "lint:fix": "biome check --apply src/", + "format": "biome format src/", + "format:fix": "biome format --write src/" } } diff --git a/packages/plugin-udio/biome.json b/packages/plugin-udio/biome.json new file mode 100644 index 00000000000..818716a6219 --- /dev/null +++ b/packages/plugin-udio/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "organizeImports": { + "enabled": false + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": "error" + }, + "suspicious": { + "noExplicitAny": "error" + }, + "style": { + "useConst": "error", + "useImportType": "off" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "es5" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-udio/eslint.config.mjs b/packages/plugin-udio/eslint.config.mjs deleted file mode 100644 index 92fe5bbebef..00000000000 --- a/packages/plugin-udio/eslint.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import eslintGlobalConfig from "../../eslint.config.mjs"; - -export default [...eslintGlobalConfig]; diff --git a/packages/plugin-udio/package.json b/packages/plugin-udio/package.json index 12ba10cb4fe..994fda5b366 100644 --- a/packages/plugin-udio/package.json +++ b/packages/plugin-udio/package.json @@ -3,19 +3,26 @@ "version": "0.1.9", "description": "Suno AI Music Generation Plugin for Eliza", "main": "dist/index.js", + "type": "module", "types": "dist/index.d.ts", "scripts": { - "build": "tsup", + "build": "tsup --format esm --dts", + "dev": "tsup --format esm --dts --watch", + "lint": "biome check src/", + "lint:fix": "biome check --apply src/", + "format": "biome format src/", + "format:fix": "biome format --write src/", "test": "jest" }, "dependencies": { - "@elizaos/core": "^0.1.0" + "@elizaos/core": "workspace:*" }, "devDependencies": { + "@biomejs/biome": "1.5.3", "typescript": "^5.0.0", "@types/node": "^16.0.0", "jest": "^27.0.0", "@types/jest": "^27.0.0", - "tsup": "^7.2.0" + "tsup": "^8.3.5" } } diff --git a/packages/plugin-video-generation/biome.json b/packages/plugin-video-generation/biome.json new file mode 100644 index 00000000000..818716a6219 --- /dev/null +++ b/packages/plugin-video-generation/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "organizeImports": { + "enabled": false + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": "error" + }, + "suspicious": { + "noExplicitAny": "error" + }, + "style": { + "useConst": "error", + "useImportType": "off" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "es5" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-video-generation/package.json b/packages/plugin-video-generation/package.json index 8656816e84d..78626b94f12 100644 --- a/packages/plugin-video-generation/package.json +++ b/packages/plugin-video-generation/package.json @@ -22,9 +22,17 @@ "@elizaos/core": "workspace:*", "tsup": "8.3.5" }, + "devDependencies": { + "@biomejs/biome": "1.5.3", + "tsup": "^8.3.5" + }, "scripts": { "build": "tsup --format esm --dts", - "dev": "tsup --format esm --dts --watch" + "dev": "tsup --format esm --dts --watch", + "lint": "biome check src/", + "lint:fix": "biome check --apply src/", + "format": "biome format src/", + "format:fix": "biome format --write src/" }, "peerDependencies": { "whatwg-url": "7.1.0" diff --git a/packages/plugin-web-search/biome.json b/packages/plugin-web-search/biome.json new file mode 100644 index 00000000000..818716a6219 --- /dev/null +++ b/packages/plugin-web-search/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "organizeImports": { + "enabled": false + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": "error" + }, + "suspicious": { + "noExplicitAny": "error" + }, + "style": { + "useConst": "error", + "useImportType": "off" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "es5" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-web-search/eslint.config.mjs b/packages/plugin-web-search/eslint.config.mjs deleted file mode 100644 index 92fe5bbebef..00000000000 --- a/packages/plugin-web-search/eslint.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import eslintGlobalConfig from "../../eslint.config.mjs"; - -export default [...eslintGlobalConfig]; diff --git a/packages/plugin-web-search/package.json b/packages/plugin-web-search/package.json index d72a50372b7..5e73a372310 100644 --- a/packages/plugin-web-search/package.json +++ b/packages/plugin-web-search/package.json @@ -24,10 +24,16 @@ "tsup": "8.3.5", "js-tiktoken": "1.0.15" }, + "devDependencies": { + "@biomejs/biome": "1.5.3" + }, "scripts": { "build": "tsup --format esm --dts", "dev": "tsup --format esm --dts --watch", - "lint": "eslint --fix --cache ." + "lint": "biome check src/", + "lint:fix": "biome check --apply src/", + "format": "biome format src/", + "format:fix": "biome format --write src/" }, "peerDependencies": { "whatwg-url": "7.1.0" diff --git a/packages/plugin-whatsapp/biome.json b/packages/plugin-whatsapp/biome.json new file mode 100644 index 00000000000..818716a6219 --- /dev/null +++ b/packages/plugin-whatsapp/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "organizeImports": { + "enabled": false + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": "error" + }, + "suspicious": { + "noExplicitAny": "error" + }, + "style": { + "useConst": "error", + "useImportType": "off" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "es5" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-whatsapp/package.json b/packages/plugin-whatsapp/package.json index 13853dc3c14..0520cb7a9bc 100644 --- a/packages/plugin-whatsapp/package.json +++ b/packages/plugin-whatsapp/package.json @@ -23,13 +23,18 @@ "build": "tsup --format esm --dts", "dev": "tsup --format esm --dts --watch", "test": "vitest run", - "coverage": "vitest run --coverage" + "coverage": "vitest run --coverage", + "lint": "biome check src/", + "lint:fix": "biome check --apply src/", + "format": "biome format src/", + "format:fix": "biome format --write src/" }, "dependencies": { "@elizaos/core": "workspace:*", "axios": "1.7.8" }, "devDependencies": { + "@biomejs/biome": "1.5.3", "@types/node": "20.17.9", "@typescript-eslint/eslint-plugin": "8.16.0", "@typescript-eslint/parser": "8.16.0", diff --git a/packages/plugin-zerion/biome.json b/packages/plugin-zerion/biome.json new file mode 100644 index 00000000000..818716a6219 --- /dev/null +++ b/packages/plugin-zerion/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "organizeImports": { + "enabled": false + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": "error" + }, + "suspicious": { + "noExplicitAny": "error" + }, + "style": { + "useConst": "error", + "useImportType": "off" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "es5" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-zerion/eslint.config.mjs b/packages/plugin-zerion/eslint.config.mjs deleted file mode 100644 index 92fe5bbebef..00000000000 --- a/packages/plugin-zerion/eslint.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import eslintGlobalConfig from "../../eslint.config.mjs"; - -export default [...eslintGlobalConfig]; diff --git a/packages/plugin-zerion/package.json b/packages/plugin-zerion/package.json index e4dc7d34803..6d668a0a138 100644 --- a/packages/plugin-zerion/package.json +++ b/packages/plugin-zerion/package.json @@ -8,11 +8,15 @@ "@elizaos/core": "workspace:*" }, "devDependencies": { + "@biomejs/biome": "1.5.3", "tsup": "^8.3.5" }, "scripts": { "build": "tsup --format esm --dts", "dev": "tsup --format esm --dts --watch", - "lint": "eslint --fix --cache ." + "lint": "biome check src/", + "lint:fix": "biome check --apply src/", + "format": "biome format src/", + "format:fix": "biome format --write src/" } } diff --git a/packages/plugin-zilliqa/biome.json b/packages/plugin-zilliqa/biome.json new file mode 100644 index 00000000000..818716a6219 --- /dev/null +++ b/packages/plugin-zilliqa/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "organizeImports": { + "enabled": false + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": "error" + }, + "suspicious": { + "noExplicitAny": "error" + }, + "style": { + "useConst": "error", + "useImportType": "off" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "es5" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-zilliqa/package.json b/packages/plugin-zilliqa/package.json index b83ac6845b8..ed4c74e6279 100644 --- a/packages/plugin-zilliqa/package.json +++ b/packages/plugin-zilliqa/package.json @@ -16,9 +16,16 @@ "@zilliqa-js/zilliqa": "^3.5.0", "tsup": "8.3.5" }, + "devDependencies": { + "@biomejs/biome": "1.5.3" + }, "scripts": { "build": "tsup --format esm --dts", - "dev": "tsup --format esm --dts --watch" + "dev": "tsup --format esm --dts --watch", + "lint": "biome check src/", + "lint:fix": "biome check --apply src/", + "format": "biome format src/", + "format:fix": "biome format --write src/" }, "peerDependencies": { "whatwg-url": "7.1.0" diff --git a/packages/plugin-zksync-era/biome.json b/packages/plugin-zksync-era/biome.json new file mode 100644 index 00000000000..818716a6219 --- /dev/null +++ b/packages/plugin-zksync-era/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "organizeImports": { + "enabled": false + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": "error" + }, + "suspicious": { + "noExplicitAny": "error" + }, + "style": { + "useConst": "error", + "useImportType": "off" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "es5" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-zksync-era/eslint.config.mjs b/packages/plugin-zksync-era/eslint.config.mjs deleted file mode 100644 index 92fe5bbebef..00000000000 --- a/packages/plugin-zksync-era/eslint.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import eslintGlobalConfig from "../../eslint.config.mjs"; - -export default [...eslintGlobalConfig]; diff --git a/packages/plugin-zksync-era/package.json b/packages/plugin-zksync-era/package.json index 3ef16373690..da568e5fdbe 100644 --- a/packages/plugin-zksync-era/package.json +++ b/packages/plugin-zksync-era/package.json @@ -23,9 +23,15 @@ "tsup": "^8.3.5", "viem": "2.22.2" }, + "devDependencies": { + "@biomejs/biome": "1.5.3" + }, "scripts": { "build": "tsup --format esm --dts", - "lint": "eslint --fix --cache ." + "lint": "biome check src/", + "lint:fix": "biome check --apply src/", + "format": "biome format src/", + "format:fix": "biome format --write src/" }, "peerDependencies": { "whatwg-url": "7.1.0"