import { tool } from "ai"; import { z } from "zod"; import { queryBirdeye } from "./client.js"; const OHLCVTimeframe = { OneMinute: "1m", ThreeMinutes: "3m", FiveMinutes: "5m", FifteenMinutes: "15m", ThirtyMinutes: "30m", OneHour: "1H", TwoHours: "2H", FourHours: "4H", SixHours: "6H", EightHours: "8H", TwelveHours: "12H", OneDay: "1D", ThreeDays: "3D", OneWeek: "1W", OneMonth: "1M", }; const parseTimeString = (timeString) => { //console.log(timeString); const now = new Date(); if (timeString === "NOW") { return Math.floor(now.getTime() / 1000); // Return in seconds for consistency } const regex = /^(\d+)([smHDWMy])$/; const match = timeString.match(regex); if (!match) { throw new Error("Invalid time string format"); } const value = parseInt(match[1]); const unit = match[2]; switch (unit) { case "m": // minutes now.setMinutes(now.getMinutes() - value); break; case "H": // hours now.setHours(now.getHours() - value); break; case "D": // days now.setDate(now.getDate() - value); break; case "W": // weeks now.setDate(now.getDate() - value * 7); break; case "M": // months now.setMonth(now.getMonth() - value); break; case "Y": // years now.setFullYear(now.getFullYear() - value); break; default: return Math.floor(now.getTime() / 1000); } return Math.floor(now.getTime() / 1000); }; const timeSchema = z.union([ z.literal("NOW"), z.enum(Object.values(OHLCVTimeframe)), ]); const timeEnum = z.enum([ "NOW", OHLCVTimeframe.OneMinute, OHLCVTimeframe.ThreeMinutes, OHLCVTimeframe.FiveMinutes, OHLCVTimeframe.FifteenMinutes, OHLCVTimeframe.ThirtyMinutes, OHLCVTimeframe.OneHour, OHLCVTimeframe.TwoHours, OHLCVTimeframe.FourHours, OHLCVTimeframe.SixHours, OHLCVTimeframe.EightHours, OHLCVTimeframe.TwelveHours, OHLCVTimeframe.OneDay, OHLCVTimeframe.ThreeDays, OHLCVTimeframe.OneWeek, OHLCVTimeframe.OneMonth, ]); const fetchOHLCVData = async ({ address, timeframe = OHLCVTimeframe.FifteenMinutes, timeFrom, timeTo, }) => { const timeFromTimestamp = parseTimeString(timeFrom); const timeToTimestamp = parseTimeString(timeTo); //console.log(`Fetching OHLCV data for address: ${address}, timeframe: ${timeframe}, from: ${timeFromTimestamp} to: ${timeToTimestamp}`); const response = await queryBirdeye("defi/ohlcv", { address, type: timeframe, time_from: timeFromTimestamp, time_to: timeToTimestamp, }); if (response?.items) { const last25Items = response.items.slice(-25); // console.log(last25Items); return { type: timeframe, items: last25Items.map(({ address, type, ...rest }) => rest), }; } return []; }; export const getFetchOHLCVTool = (userId, chatId, responseId, dataStream) => tool({ description: "Fetch OHLCV (Open, High, Low, Close, Volume) data for a given address and timeframe. The function returns 25 candlesticks maximum, so be sure to choose your time parameters wisely.", parameters: z.object({ address: z.string().describe("The address to fetch OHLCV data for"), timeframe: timeSchema .describe("The timeframe for the OHLCV data (default is 15 minutes)") .optional() .default("15m"), timeFrom: timeEnum.describe( "The start time, either a timeframe or 'NOW'", ), timeTo: timeEnum.describe("The end time, either a timeframe or 'NOW'"), }), execute: async ({ address, timeframe, timeFrom, timeTo }) => { try { console.log( `${userId} ${chatId} called fetchOHLCVData with address: ${address} | timeframe: ${timeframe} | timeFrom: ${timeFrom} | timeTo: ${timeTo}`, ); const ohlcvData = await fetchOHLCVData({ address, timeframe, timeFrom, timeTo, }); if (ohlcvData) { dataStream.writeMessageAnnotation({ id: responseId, tool_type: 'chart', content: address, }); } return JSON.stringify(ohlcvData); } catch (error) { console.error( `Error occurred while fetching OHLCV data: ${error.message}`, ); return "Failed to fetch OHLCV data."; } }, });