diff --git a/package.json b/package.json index 6def6f7a921..5f2dee536eb 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@coinbase/coinbase-sdk": "^0.10.0", "csv-parse": "^5.6.0", "@0glabs/0g-ts-sdk": "^0.2.1", + "amqplib": "^0.10.4", "ollama-ai-provider": "^0.16.1", "optional": "^0.1.4", "sharp": "^0.33.5", diff --git a/packages/plugin-solana/src/evaluators/trust.ts b/packages/plugin-solana/src/evaluators/trust.ts index e8feaae03a7..75e470c87a1 100644 --- a/packages/plugin-solana/src/evaluators/trust.ts +++ b/packages/plugin-solana/src/evaluators/trust.ts @@ -253,6 +253,7 @@ async function handler(runtime: IAgentRuntime, message: Memory) { runtime, rec.contractAddress, userId, + account.username, // we need this to create the recommender account in the BE { buy_amount: rec.buyAmount, is_simulation: true, diff --git a/packages/plugin-solana/src/providers/simulationSellingService.ts b/packages/plugin-solana/src/providers/simulationSellingService.ts new file mode 100644 index 00000000000..d3d7ce267d3 --- /dev/null +++ b/packages/plugin-solana/src/providers/simulationSellingService.ts @@ -0,0 +1,442 @@ +import { + TrustScoreDatabase, + TokenPerformance, + TradePerformance, + TokenRecommendation, + ProcessedTokenData, +} from "@ai16z/plugin-trustdb"; +import { Connection, PublicKey } from "@solana/web3.js"; +// Assuming TokenProvider and IAgentRuntime are available +import { TokenProvider } from "./token.ts"; +import { settings } from "@ai16z/eliza"; +import { IAgentRuntime, Memory, Provider, State } from "@ai16z/eliza"; +import { WalletProvider } from "./wallet.ts"; +import * as amqp from "amqplib"; + +interface SellDetails { + sell_amount: number; + sell_recommender_id: string | null; +} + +export class simulationSellingService { + private trustScoreDb: TrustScoreDatabase; + private walletProvider: WalletProvider; + private connection: Connection; + private baseMint: PublicKey; + private DECAY_RATE = 0.95; + private MAX_DECAY_DAYS = 30; + private backend: string; + private backendToken: string; + private amqpConnection: amqp.Connection; + private amqpChannel: amqp.Channel; + private sonarBe: string; + private sonarBeToken: string; + + private runningProcesses: Set = new Set(); + + constructor( + runtime: IAgentRuntime, + trustScoreDb: TrustScoreDatabase, + walletProvider: WalletProvider + ) { + this.trustScoreDb = trustScoreDb; + + this.connection = new Connection(runtime.getSetting("RPC_URL")); + this.walletProvider = new WalletProvider( + this.connection, + new PublicKey(runtime.getSetting("WALLET_PUBLIC_KEY")) + ); + this.baseMint = new PublicKey( + runtime.getSetting("BASE_MINT") || + "So11111111111111111111111111111111111111112" + ); + this.backend = runtime.getSetting("BACKEND_URL"); + this.backendToken = runtime.getSetting("BACKEND_TOKEN"); + this.initializeRabbitMQ(runtime.getSetting("AMQP_URL")); + this.sonarBe = runtime.getSetting("SONAR_BE"); + this.sonarBeToken = runtime.getSetting("SONAR_BE_TOKEN"); + } + /** + * Initializes the RabbitMQ connection and starts consuming messages. + * @param amqpUrl The RabbitMQ server URL. + */ + private async initializeRabbitMQ(amqpUrl: string) { + try { + this.amqpConnection = await amqp.connect(amqpUrl); + this.amqpChannel = await this.amqpConnection.createChannel(); + console.log("Connected to RabbitMQ"); + // Start consuming messages + this.consumeMessages(); + } catch (error) { + console.error("Failed to connect to RabbitMQ:", error); + } + } + + /** + * Sets up the consumer for the specified RabbitMQ queue. + */ + private async consumeMessages() { + const queue = "process_eliza_simulation"; + await this.amqpChannel.assertQueue(queue, { durable: true }); + this.amqpChannel.consume( + queue, + (msg) => { + if (msg !== null) { + const content = msg.content.toString(); + this.processMessage(content); + this.amqpChannel.ack(msg); + } + }, + { noAck: false } + ); + console.log(`Listening for messages on queue: ${queue}`); + } + + /** + * Processes incoming messages from RabbitMQ. + * @param message The message content as a string. + */ + private async processMessage(message: string) { + try { + const { tokenAddress, amount, sell_recommender_id } = + JSON.parse(message); + console.log( + `Received message for token ${tokenAddress} to sell ${amount}` + ); + + const decision: SellDecision = { + tokenPerformance: + await this.trustScoreDb.getTokenPerformance(tokenAddress), + amountToSell: amount, + sell_recommender_id: sell_recommender_id, + }; + + // Execute the sell + await this.executeSellDecision(decision); + + // Remove from running processes after completion + this.runningProcesses.delete(tokenAddress); + } catch (error) { + console.error("Error processing message:", error); + } + } + + /** + * Executes a single sell decision. + * @param decision The sell decision containing token performance and amount to sell. + */ + private async executeSellDecision(decision: SellDecision) { + const { tokenPerformance, amountToSell, sell_recommender_id } = + decision; + const tokenAddress = tokenPerformance.tokenAddress; + + try { + console.log( + `Executing sell for token ${tokenPerformance.tokenSymbol}: ${amountToSell}` + ); + + // Update the sell details + const sellDetails: SellDetails = { + sell_amount: amountToSell, + sell_recommender_id: sell_recommender_id, // Adjust if necessary + }; + const sellTimeStamp = new Date().toISOString(); + const tokenProvider = new TokenProvider( + tokenAddress, + this.walletProvider + ); + + // Update sell details in the database + const sellDetailsData = await this.updateSellDetails( + tokenAddress, + tokenPerformance.recommenderId, + sellTimeStamp, + sellDetails, + true, // isSimulation + tokenProvider + ); + + console.log("Sell order executed successfully", sellDetailsData); + + // check if balance is zero and remove token from running processes + const balance = this.trustScoreDb.getTokenBalance(tokenAddress); + if (balance === 0) { + this.runningProcesses.delete(tokenAddress); + } + // stop the process in the sonar backend + await this.stopProcessInTheSonarBackend(tokenAddress); + } catch (error) { + console.error( + `Error executing sell for token ${tokenAddress}:`, + error + ); + } + } + + public async startService() { + // starting the service + console.log("Starting SellingService..."); + await this.startListeners(); + } + + private async startListeners() { + // scanning recommendations and selling + console.log("Scanning for token performances..."); + const tokenPerformances = + await this.trustScoreDb.getAllTokenPerformancesWithBalance(); + + await this.processTokenPerformances(tokenPerformances); + } + + private processTokenPerformances(tokenPerformances: TokenPerformance[]) { + // To Do: logic when to sell and how much + console.log("Deciding when to sell and how much..."); + const runningProcesses = this.runningProcesses; + // remove running processes from tokenPerformances + tokenPerformances = tokenPerformances.filter( + (tp) => !runningProcesses.has(tp.tokenAddress) + ); + + // start the process in the sonar backend + tokenPerformances.forEach(async (tokenPerformance) => { + const tokenProvider = new TokenProvider( + tokenPerformance.tokenAddress, + this.walletProvider + ); + const shouldTrade = await tokenProvider.shouldTradeToken(); + if (shouldTrade) { + const balance = tokenPerformance.balance; + const sell_recommender_id = tokenPerformance.recommenderId; + const tokenAddress = tokenPerformance.tokenAddress; + const process = await this.startProcessInTheSonarBackend( + tokenAddress, + balance, + sell_recommender_id, + tokenPerformance.initial_mc + ); + if (process) { + this.runningProcesses.add(tokenAddress); + } + } + }); + } + + private async startProcessInTheSonarBackend( + tokenAddress: string, + balance: number, + isSimulation: boolean, + initial_mc: number + ) { + try { + const message = JSON.stringify({ + tokenAddress, + balance, + isSimulation, + initial_mc, + }); + const response = await fetch( + `${this.sonarBe}/ai16z-sol/startProcess`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-api-key": `${this.sonarBeToken}`, + }, + body: message, + } + ); + + if (!response.ok) { + console.error( + `Failed to send message to process token ${tokenAddress}` + ); + return; + } + + const result = await response.json(); + console.log("Received response:", result); + console.log(`Sent message to process token ${tokenAddress}`); + + return result; + } catch (error) { + console.error( + `Error sending message to process token ${tokenAddress}:`, + error + ); + return null; + } + } + + private stopProcessInTheSonarBackend(tokenAddress: string) { + try { + return fetch(`${this.sonarBe}/ai16z-sol/stopProcess`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-api-key": `${this.sonarBeToken}`, + }, + body: JSON.stringify({ tokenAddress }), + }); + } catch (error) { + console.error( + `Error stopping process for token ${tokenAddress}:`, + error + ); + } + } + + async updateSellDetails( + tokenAddress: string, + recommenderId: string, + sellTimeStamp: string, + sellDetails: SellDetails, + isSimulation: boolean, + tokenProvider: TokenProvider + ) { + const recommender = + await this.trustScoreDb.getOrCreateRecommenderWithTelegramId( + recommenderId + ); + const processedData: ProcessedTokenData = + await tokenProvider.getProcessedTokenData(); + const prices = await this.walletProvider.fetchPrices(null); + const solPrice = prices.solana.usd; + const sellSol = sellDetails.sell_amount / parseFloat(solPrice); + const sell_value_usd = + sellDetails.sell_amount * processedData.tradeData.price; + const trade = await this.trustScoreDb.getLatestTradePerformance( + tokenAddress, + recommender.id, + isSimulation + ); + const buyTimeStamp = trade.buy_timeStamp; + const marketCap = + processedData.dexScreenerData.pairs[0]?.marketCap || 0; + const liquidity = + processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0; + const sell_price = processedData.tradeData.price; + const profit_usd = sell_value_usd - trade.buy_value_usd; + const profit_percent = (profit_usd / trade.buy_value_usd) * 100; + + const market_cap_change = marketCap - trade.buy_market_cap; + const liquidity_change = liquidity - trade.buy_liquidity; + + const isRapidDump = await this.isRapidDump(tokenAddress, tokenProvider); + + const sellDetailsData = { + sell_price: sell_price, + sell_timeStamp: sellTimeStamp, + sell_amount: sellDetails.sell_amount, + received_sol: sellSol, + sell_value_usd: sell_value_usd, + profit_usd: profit_usd, + profit_percent: profit_percent, + sell_market_cap: marketCap, + market_cap_change: market_cap_change, + sell_liquidity: liquidity, + liquidity_change: liquidity_change, + rapidDump: isRapidDump, + sell_recommender_id: sellDetails.sell_recommender_id || null, + }; + this.trustScoreDb.updateTradePerformanceOnSell( + tokenAddress, + recommender.id, + buyTimeStamp, + sellDetailsData, + isSimulation + ); + + // If the trade is a simulation update the balance + const oldBalance = this.trustScoreDb.getTokenBalance(tokenAddress); + const tokenBalance = oldBalance - sellDetails.sell_amount; + this.trustScoreDb.updateTokenBalance(tokenAddress, tokenBalance); + // generate some random hash for simulations + const hash = Math.random().toString(36).substring(7); + const transaction = { + tokenAddress: tokenAddress, + type: "sell", + transactionHash: hash, + amount: sellDetails.sell_amount, + price: processedData.tradeData.price, + isSimulation: true, + timestamp: new Date().toISOString(), + }; + this.trustScoreDb.addTransaction(transaction); + this.updateTradeInBe( + tokenAddress, + recommender.id, + recommender.telegramId, + sellDetailsData, + tokenBalance + ); + + return sellDetailsData; + } + async isRapidDump( + tokenAddress: string, + tokenProvider: TokenProvider + ): Promise { + const processedData: ProcessedTokenData = + await tokenProvider.getProcessedTokenData(); + console.log(`Fetched processed token data for token: ${tokenAddress}`); + + return processedData.tradeData.trade_24h_change_percent < -50; + } + + async delay(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + async updateTradeInBe( + tokenAddress: string, + recommenderId: string, + username: string, + data: SellDetails, + balanceLeft: number, + retries = 3, + delayMs = 2000 + ) { + for (let attempt = 1; attempt <= retries; attempt++) { + try { + await fetch( + `${this.backend}/api/updaters/updateTradePerformance`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.backendToken}`, + }, + body: JSON.stringify({ + tokenAddress: tokenAddress, + tradeData: data, + recommenderId: recommenderId, + username: username, + isSimulation: true, + balanceLeft: balanceLeft, + }), + } + ); + // If the request is successful, exit the loop + return; + } catch (error) { + console.error( + `Attempt ${attempt} failed: Error creating trade in backend`, + error + ); + if (attempt < retries) { + console.log(`Retrying in ${delayMs} ms...`); + await this.delay(delayMs); // Wait for the specified delay before retrying + } else { + console.error("All attempts failed."); + } + } + } + } +} + +// SellDecision interface +interface SellDecision { + tokenPerformance: TokenPerformance; + amountToSell: number; + sell_recommender_id: string | null; +} diff --git a/packages/plugin-solana/src/providers/trustScoreProvider.ts b/packages/plugin-solana/src/providers/trustScoreProvider.ts index 32f41267203..1d9c0939801 100644 --- a/packages/plugin-solana/src/providers/trustScoreProvider.ts +++ b/packages/plugin-solana/src/providers/trustScoreProvider.ts @@ -338,6 +338,7 @@ export class TrustScoreManager { runtime: IAgentRuntime, tokenAddress: string, recommenderId: string, + username: string, data: TradeData ): Promise { const recommender = @@ -351,10 +352,14 @@ export class TrustScoreManager { new PublicKey(Wallet!) ); + let tokensBalance = 0; const prices = await wallet.fetchPrices(runtime); const solPrice = prices.solana.usd; const buySol = data.buy_amount / parseFloat(solPrice); const buy_value_usd = data.buy_amount * processedData.tradeData.price; + const token = await this.tokenProvider.fetchTokenTradeData(); + const tokenPrice = token.price; + tokensBalance = buy_value_usd / tokenPrice; const creationData = { token_address: tokenAddress, @@ -383,8 +388,60 @@ export class TrustScoreManager { rapidDump: false, }; this.trustScoreDb.addTradePerformance(creationData, data.is_simulation); + + const tokenRecommendation: TokenRecommendation = { + recommenderId: recommenderId, + tokenAddress: tokenAddress, + timestamp: new Date(), + initialMarketCap: + processedData.dexScreenerData.pairs[0]?.marketCap || 0, + initialLiquidity: + processedData.dexScreenerData.pairs[0]?.liquidity || 0, + initialPrice: processedData.tradeData.price, + }; + this.trustScoreDb.addTokenRecommendation(tokenRecommendation); + + this.trustScoreDb.upsertTokenPerformance({ + tokenAddress: tokenAddress, + priceChange24h: processedData.tradeData.price_change_24h_percent, + volumeChange24h: processedData.tradeData.volume_24h, + trade_24h_change: processedData.tradeData.trade_24h_change_percent, + liquidity: + processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0, + liquidityChange24h: 0, + holderChange24h: + processedData.tradeData.unique_wallet_24h_change_percent, + rugPull: false, + isScam: false, + marketCapChange24h: 0, + sustainedGrowth: false, + rapidDump: false, + suspiciousVolume: false, + validationTrust: 0, + balance: tokensBalance, + initialMarketCap: + processedData.dexScreenerData.pairs[0]?.marketCap || 0, + lastUpdated: new Date(), + }); + + if (data.is_simulation) { + // If the trade is a simulation update the balance + this.trustScoreDb.updateTokenBalance(tokenAddress, tokensBalance); + // generate some random hash for simulations + const hash = Math.random().toString(36).substring(7); + const transaction = { + tokenAddress: tokenAddress, + type: "buy", + transactionHash: hash, + amount: data.buy_amount, + price: processedData.tradeData.price, + isSimulation: true, + timestamp: new Date().toISOString(), + }; + this.trustScoreDb.addTransaction(transaction); + } // api call to update trade performance - this.createTradeInBe(tokenAddress, recommenderId, data); + this.createTradeInBe(tokenAddress, recommenderId, username, data); return creationData; } @@ -395,6 +452,7 @@ export class TrustScoreManager { async createTradeInBe( tokenAddress: string, recommenderId: string, + username: string, data: TradeData, retries = 3, delayMs = 2000 @@ -413,6 +471,7 @@ export class TrustScoreManager { tokenAddress: tokenAddress, tradeData: data, recommenderId: recommenderId, + username: username, }), } ); @@ -507,6 +566,25 @@ export class TrustScoreManager { sellDetailsData, isSimulation ); + if (isSimulation) { + // If the trade is a simulation update the balance + const oldBalance = this.trustScoreDb.getTokenBalance(tokenAddress); + const tokenBalance = oldBalance - sellDetails.sell_amount; + this.trustScoreDb.updateTokenBalance(tokenAddress, tokenBalance); + // generate some random hash for simulations + const hash = Math.random().toString(36).substring(7); + const transaction = { + tokenAddress: tokenAddress, + type: "sell", + transactionHash: hash, + amount: sellDetails.sell_amount, + price: processedData.tradeData.price, + isSimulation: true, + timestamp: new Date().toISOString(), + }; + this.trustScoreDb.addTransaction(transaction); + } + return sellDetailsData; } diff --git a/packages/plugin-trustdb/src/adapters/trustScoreDatabase.ts b/packages/plugin-trustdb/src/adapters/trustScoreDatabase.ts index 51b44b9eb25..b877d52e8b5 100644 --- a/packages/plugin-trustdb/src/adapters/trustScoreDatabase.ts +++ b/packages/plugin-trustdb/src/adapters/trustScoreDatabase.ts @@ -40,6 +40,8 @@ export interface TokenPerformance { rapidDump: boolean; suspiciousVolume: boolean; validationTrust: number; + balance: number; + initialMarketCap: number; lastUpdated: Date; } @@ -120,9 +122,21 @@ interface TokenPerformanceRow { rapid_dump: number; suspicious_volume: number; validation_trust: number; + balance: number; + initial_market_cap: number; last_updated: string; } +interface Transaction { + tokenAddress: string; + transactionHash: string; + type: "buy" | "sell"; + amount: number; + price: number; + isSimulation: boolean; + timestamp: string; +} + export class TrustScoreDatabase { private db: Database; @@ -192,6 +206,8 @@ export class TrustScoreDatabase { rapid_dump BOOLEAN DEFAULT FALSE, suspicious_volume BOOLEAN DEFAULT FALSE, validation_trust REAL DEFAULT 0, + balance REAL DEFAULT 0, + initial_market_cap REAL DEFAULT 0, last_updated DATETIME DEFAULT CURRENT_TIMESTAMP ); `); @@ -289,6 +305,20 @@ export class TrustScoreDatabase { FOREIGN KEY (recommender_id) REFERENCES recommenders(id) ON DELETE CASCADE ); `); + + // create transactions table + this.db.exec(` + CREATE TABLE IF NOT EXISTS transactions ( + token_address TEXT NOT NULL, + transaction_hash TEXT PRIMARY KEY, + type TEXT NOT NULL, + amount REAL NOT NULL, + price REAL NOT NULL, + timestamp TEXT NOT NULL, + is_simulation BOOLEAN DEFAULT FALSE, + FOREIGN KEY (token_address) REFERENCES token_performance(token_address) ON DELETE CASCADE + ); + `); } /** @@ -704,6 +734,8 @@ export class TrustScoreDatabase { rapid_dump, suspicious_volume, validation_trust, + balance, + initial_market_cap, last_updated ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT(token_address) DO UPDATE SET @@ -720,6 +752,8 @@ export class TrustScoreDatabase { rapid_dump = excluded.rapid_dump, suspicious_volume = excluded.suspicious_volume, validation_trust = excluded.validation_trust, + balance = excluded.balance, + initial_market_cap = excluded.initial_market_cap, last_updated = CURRENT_TIMESTAMP; `; try { @@ -737,6 +771,8 @@ export class TrustScoreDatabase { performance.sustainedGrowth ? 1 : 0, performance.rapidDump ? 1 : 0, performance.suspiciousVolume ? 1 : 0, + performance.balance, + performance.initialMarketCap, validationTrust ); console.log( @@ -749,6 +785,25 @@ export class TrustScoreDatabase { } } + // update token balance + + updateTokenBalance(tokenAddress: string, balance: number): boolean { + const sql = ` + UPDATE token_performance + SET balance = ?, + last_updated = CURRENT_TIMESTAMP + WHERE token_address = ?; + `; + try { + this.db.prepare(sql).run(balance, tokenAddress); + console.log(`Updated token balance for ${tokenAddress}`); + return true; + } catch (error) { + console.error("Error updating token balance:", error); + return false; + } + } + /** * Retrieves token performance metrics. * @param tokenAddress Token's address @@ -776,10 +831,46 @@ export class TrustScoreDatabase { rapidDump: row.rapid_dump === 1, suspiciousVolume: row.suspicious_volume === 1, validationTrust: row.validation_trust, + balance: row.balance, + initialMarketCap: row.initial_market_cap, lastUpdated: new Date(row.last_updated), }; } + //getTokenBalance + getTokenBalance(tokenAddress: string): number { + const sql = `SELECT balance FROM token_performance WHERE token_address = ?;`; + const row = this.db.prepare(sql).get(tokenAddress) as { + balance: number; + }; + return row.balance; + } + + getAllTokenPerformancesWithBalance(): TokenPerformance[] { + const sql = `SELECT * FROM token_performance WHERE balance > 0;`; + const rows = this.db.prepare(sql).all() as TokenPerformanceRow[]; + + return rows.map((row) => ({ + tokenAddress: row.token_address, + priceChange24h: row.price_change_24h, + volumeChange24h: row.volume_change_24h, + trade_24h_change: row.trade_24h_change, + liquidity: row.liquidity, + liquidityChange24h: row.liquidity_change_24h, + holderChange24h: row.holder_change_24h, + rugPull: row.rug_pull === 1, + isScam: row.is_scam === 1, + marketCapChange24h: row.market_cap_change24h, + sustainedGrowth: row.sustained_growth === 1, + rapidDump: row.rapid_dump === 1, + suspiciousVolume: row.suspicious_volume === 1, + validationTrust: row.validation_trust, + balance: row.balance, + initialMarketCap: row.initial_market_cap, + lastUpdated: new Date(row.last_updated), + })); + } + // ----- TokenRecommendations Methods ----- /** @@ -1247,6 +1338,79 @@ export class TrustScoreDatabase { }; } + // ----- Transactions Methods ----- + /** + * Adds a new transaction to the database. + * @param transaction Transaction object + * @returns boolean indicating success + */ + + addTransaction(transaction: Transaction): boolean { + const sql = ` + INSERT INTO transactions ( + token_address, + transaction_hash, + type, + amount, + price, + is_simulation, + timestamp + ) VALUES (?, ?, ?, ?, ?, ?); + `; + try { + this.db + .prepare(sql) + .run( + transaction.tokenAddress, + transaction.transactionHash, + transaction.type, + transaction.amount, + transaction.price, + transaction.isSimulation, + transaction.timestamp + ); + return true; + } catch (error) { + console.error("Error adding transaction:", error); + return false; + } + } + + /** + * Retrieves all transactions for a specific token. + * @param tokenAddress Token's address + * @returns Array of Transaction objects + */ + getTransactionsByToken(tokenAddress: string): Transaction[] { + const sql = `SELECT * FROM transactions WHERE token_address = ? ORDER BY timestamp DESC;`; + const rows = this.db.prepare(sql).all(tokenAddress) as Array<{ + token_address: string; + transaction_hash: string; + type: string; + amount: number; + price: number; + is_simulation: boolean; + timestamp: string; + }>; + + return rows.map((row) => { + // Validate and cast 'type' to ensure it matches the expected union type + if (row.type !== "buy" && row.type !== "sell") { + throw new Error(`Unexpected transaction type: ${row.type}`); + } + + return { + tokenAddress: row.token_address, + transactionHash: row.transaction_hash, + type: row.type as "buy" | "sell", + amount: row.amount, + price: row.price, + isSimulation: row.is_simulation, + timestamp: new Date(row.timestamp).toISOString(), + }; + }); + } + /** * Close the database connection gracefully. */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 44906db78fa..e8a3d8a76dd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: csv-parse: specifier: ^5.6.0 version: 5.6.0 + amqplib: + specifier: ^0.10.4 + version: 0.10.4 ollama-ai-provider: specifier: ^0.16.1 version: 0.16.1(zod@3.23.8) @@ -2932,6 +2935,7 @@ packages: resolution: {integrity: sha512-hArn9FF5ZYi1IkxdJEVnJi+OxlwLV0NJYWpKXsmNOojtGtAZHxmsELA+MZlu2KW1F/K1/nt7lFOfcMXNYweq9w==} version: 0.17.0 engines: {node: '>=16.11.0'} + deprecated: This version uses deprecated encryption modes. Please use a newer version. '@discordjs/ws@1.1.1': resolution: {integrity: sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==} @@ -6629,6 +6633,10 @@ packages: amp@0.3.1: resolution: {integrity: sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw==} + amqplib@0.10.4: + resolution: {integrity: sha512-DMZ4eCEjAVdX1II2TfIUpJhfKAuoCeDIo/YyETbfAqehHTXxxs7WOOd+N1Xxr4cKhx12y23zk8/os98FxlZHrw==} + engines: {node: '>=10'} + ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} @@ -7088,6 +7096,9 @@ packages: resolution: {integrity: sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA==} engines: {node: '>=4.5'} + buffer-more-ints@1.0.0: + resolution: {integrity: sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -22691,6 +22702,15 @@ snapshots: amp@0.3.1: {} + amqplib@0.10.4: + dependencies: + '@acuminous/bitsyntax': 0.1.2 + buffer-more-ints: 1.0.0 + readable-stream: 1.1.14 + url-parse: 1.5.10 + transitivePeerDependencies: + - supports-color + ansi-align@3.0.1: dependencies: string-width: 4.2.3 @@ -23217,6 +23237,8 @@ snapshots: buffer-layout@1.2.2: {} + buffer-more-ints@1.0.0: {} + buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -32310,6 +32332,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.26.0) + esbuild: 0.24.0 ts-mixer@6.0.4: {}